diff --git a/src/rp2040/CMakeLists.txt b/src/rp2040/CMakeLists.txt index a80dc9d..bf256b2 100644 --- a/src/rp2040/CMakeLists.txt +++ b/src/rp2040/CMakeLists.txt @@ -1,3 +1,6 @@ +# to build for waveshare rp2040-zero do +# cmake -DWS2040_ZERO=ON .. + cmake_minimum_required(VERSION 3.13) set(PROJECT fpga_companion) @@ -47,6 +50,21 @@ family_add_pico_pio_usb(${PROJECT}) target_compile_definitions(${PROJECT} PRIVATE PIO_USB_DP_PIN_DEFAULT=2 ) + +option(WS2040_ZERO "Build for Waveshare RP2040-Zero" OFF) # Regular Pico by default +if(WS2040_ZERO) + add_compile_definitions(${PROJECT} PRIVATE WAVESHARE_RP2040_ZERO=1) + add_custom_command(TARGET ${PROJECT} POST_BUILD COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "Firmware has been built for Waveshare RP2040-Zero.") +else(WS2040_ZERO) + add_custom_command(TARGET ${PROJECT} POST_BUILD COMMAND ${CMAKE_COMMAND} -E cmake_echo_color --cyan "Firmware has been built for Raspberry Pi Pico or Pico-W.") +endif(WS2040_ZERO) + +target_include_directories(${PROJECT} PRIVATE + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/../fatfs/source + ${CMAKE_CURRENT_LIST_DIR}/../u8g2/csrc + ${CMAKE_CURRENT_LIST_DIR}/../tusb_xinput +) target_include_directories(${PROJECT} PRIVATE ${CMAKE_CURRENT_LIST_DIR} diff --git a/src/rp2040/README.md b/src/rp2040/README.md index 6d363aa..ad574d2 100644 --- a/src/rp2040/README.md +++ b/src/rp2040/README.md @@ -95,6 +95,43 @@ To use this you need a micro-USB to USB-A-OTG adapter. ![Tang Nano 20k with Raspberry Pi Pico](pico_tn20k.png) +# Using the Waveshare RP2040-Zero + +The Raspberry Pi Pico is rather big and only comes with a Micro USB +port. The [Waveshare RP2040-Zero](https://www.waveshare.com/rp2040-zero.htm) is a little +smaller and comes with a USB-C connector which makes it easier to use +it as a USB host using a regular USB-C to USB-A host adapter. + +To build the firmware for the RP2040-Zero use the following +command: + +``` +cmake -DWS2040_ZERO=ON .. +``` + +The build process will then end with the following message: + +``` +Firmware has been built for Waveshare RP2040-Zero. +``` + +The SPI pins used on the RP2040-Zero differ from the ones used on the +regular pico: + +| Pin | Signal | Description | +|---|---|---| +| GP0 | UART_TX | Serial debug output | +| GP4 | MISO | SPI data from FPGA | +| GP5 | CSn | SPI chip select to FPGA | +| GP6 | SCK | SPI clock to FPGA | +| GP7 | MOSI | SPI data to FPGA | +| GP8 | IRQn | SPI interrupt from FPGA | + +Also the RP2040-Zero comes with a WS2812 RGB led instead of a regular +LED. Driving the RGB LED requies a PIO unit and thus cannot be used at +the same time as the PIO-USB. The native USB of the RP2040-Zero must +therefore always be used, anyway. + # Convenient development The RP2040 is by default somewhat inconvenient to develop for diff --git a/src/rp2040/mcu_hw.c b/src/rp2040/mcu_hw.c index 7c42802..d030e33 100644 --- a/src/rp2040/mcu_hw.c +++ b/src/rp2040/mcu_hw.c @@ -22,6 +22,34 @@ #include "../mcu_hw.h" +#ifdef WAVESHARE_RP2040_ZERO +#warning "Building for Waveshare RP2040-Zero mini board" + +// the waveshare mini does not expose the default spi0 pins, so we need +// to specify them +#define SPI_RX_PIN 4 +#define SPI_SCK_PIN 6 +#define SPI_TX_PIN 7 +#define SPI_CSN_PIN 5 +#define SPI_IRQ_PIN 8 +#define SPI_BUS spi0 +#define WS2812_PIN 16 +#else +#warning "Building for Pi Pico and Pico(W)" + +// the regular pi pico uses spi0 by default +#define SPI_RX_PIN PICO_DEFAULT_SPI_RX_PIN +#define SPI_SCK_PIN PICO_DEFAULT_SPI_SCK_PIN +#define SPI_TX_PIN PICO_DEFAULT_SPI_TX_PIN +#define SPI_CSN_PIN PICO_DEFAULT_SPI_CSN_PIN +#define SPI_IRQ_PIN 22 +#define SPI_BUS spi_default +#endif + +#ifdef WS2812_PIN +#include "ws2812.pio.h" +#endif + /* ============================================================================================= */ /* =============== USB ============== */ /* ============================================================================================= */ @@ -38,6 +66,11 @@ #error "Please update your TinyUSB installation!" #endif +#include "tusb_config.h" +#if defined(WS2812_PIN) && CFG_TUH_RPI_PIO_USB == 1 +#error "WS2812B and PIO USB cannot be used simultaneously!" +#endif + static struct { uint8_t dev_addr; uint8_t instance; @@ -137,7 +170,7 @@ static SemaphoreHandle_t sem; static void irq_handler(void) { // Disable interrupt. It will be re-enabled by the com task - gpio_set_irq_enabled(22, GPIO_IRQ_LEVEL_LOW, false); + gpio_set_irq_enabled(SPI_IRQ_PIN, GPIO_IRQ_LEVEL_LOW, false); if(com_task_handle) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; @@ -152,27 +185,27 @@ void mcu_hw_spi_init(void) { sem = xSemaphoreCreateMutex(); // init SPI at 20Mhz, mode 1 - spi_init(spi_default, 20000000); - spi_set_format(spi_default, 8, SPI_CPOL_0, SPI_CPHA_1, SPI_MSB_FIRST); + spi_init(SPI_BUS, 20000000); + spi_set_format(SPI_BUS, 8, SPI_CPOL_0, SPI_CPHA_1, SPI_MSB_FIRST); - debugf(" MISO = %d", PICO_DEFAULT_SPI_RX_PIN); - gpio_set_function(PICO_DEFAULT_SPI_RX_PIN, GPIO_FUNC_SPI); - debugf(" SCK = %d", PICO_DEFAULT_SPI_SCK_PIN); - gpio_set_function(PICO_DEFAULT_SPI_SCK_PIN, GPIO_FUNC_SPI); - debugf(" MOSI = %d", PICO_DEFAULT_SPI_TX_PIN); - gpio_set_function(PICO_DEFAULT_SPI_TX_PIN, GPIO_FUNC_SPI); + debugf(" MISO = %d", SPI_RX_PIN); + gpio_set_function(SPI_RX_PIN, GPIO_FUNC_SPI); + debugf(" SCK = %d", SPI_SCK_PIN); + gpio_set_function(SPI_SCK_PIN, GPIO_FUNC_SPI); + debugf(" MOSI = %d", SPI_TX_PIN); + gpio_set_function(SPI_TX_PIN, GPIO_FUNC_SPI); // Chip select is active-low, so we'll initialise it to a driven-high state - debugf(" CSn = %d", PICO_DEFAULT_SPI_CSN_PIN); - gpio_init(PICO_DEFAULT_SPI_CSN_PIN); - gpio_set_dir(PICO_DEFAULT_SPI_CSN_PIN, GPIO_OUT); - gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 1); + debugf(" CSn = %d", SPI_CSN_PIN); + gpio_init(SPI_CSN_PIN); + gpio_set_dir(SPI_CSN_PIN, GPIO_OUT); + gpio_put(SPI_CSN_PIN, 1); // The interruput input isn't strictly part of the SPi // The interrupt is active low on GP22 - debugf(" IRQn = %d", 22); - // set handler but not enable yet as the main task may not be ready - gpio_add_raw_irq_handler(22, irq_handler); + debugf(" IRQn = %d", SPI_IRQ_PIN); + // set handler but not enable yet as the main task may not be ready + gpio_add_raw_irq_handler(SPI_IRQ_PIN, irq_handler); } void mcu_hw_irq_ack(void) { @@ -185,22 +218,22 @@ void mcu_hw_irq_ack(void) { } // re-enable the interrupt since it was now serviced outside the irq handler - gpio_set_irq_enabled(22, GPIO_IRQ_LEVEL_LOW, 1); + gpio_set_irq_enabled(SPI_IRQ_PIN, GPIO_IRQ_LEVEL_LOW, 1); } void mcu_hw_spi_begin() { xSemaphoreTake(sem, 0xffffffffUL); // wait forever - gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 0); // Active low + gpio_put(SPI_CSN_PIN, 0); // Active low } void mcu_hw_spi_end() { - gpio_put(PICO_DEFAULT_SPI_CSN_PIN, 1); + gpio_put(SPI_CSN_PIN, 1); xSemaphoreGive(sem); } unsigned char mcu_hw_spi_tx_u08(unsigned char b) { unsigned char retval; - spi_write_read_blocking(spi_default, &b, &retval, 1); + spi_write_read_blocking(SPI_BUS, &b, &retval, 1); return retval; } @@ -215,12 +248,21 @@ void mcu_hw_init(void) { stdio_init_all(); // ... so stdio can adjust its bit rate printf("\r\n\r\n" LOGO " FPGA Companion for RP2040\r\n\r\n"); +#if CFG_TUH_RPI_PIO_USB == 0 + printf("Using native USB\r\n"); +#else printf("USB D+/D- on GP%d and GP%d\r\n", PIO_USB_DP_PIN_DEFAULT, PIO_USB_DP_PIN_DEFAULT+1); - +#endif + +#ifdef WS2812_PIN + uint offset = pio_add_program(pio0, &ws2812_program); + ws2812_program_init(pio0, 0, offset, WS2812_PIN, 800000, 0); +#else gpio_init(PICO_DEFAULT_LED_PIN); gpio_set_dir(PICO_DEFAULT_LED_PIN, 1); gpio_put(PICO_DEFAULT_LED_PIN, !PICO_DEFAULT_LED_PIN_INVERTED); - +#endif + mcu_hw_spi_init(); tuh_init(BOARD_TUH_RHPORT); @@ -363,10 +405,18 @@ void vApplicationIdleHook( void ) { void vApplicationTickHook( void ) { } +#ifdef WS2812_PIN +static void led_timer(__attribute__((unused)) TimerHandle_t pxTimer) { + static char state = 0; + pio_sm_put_blocking(pio0, 0, state?0xff000000:0x00000000); // GRBX + state = !state; +} +#else static void led_timer(__attribute__((unused)) TimerHandle_t pxTimer) { gpio_xor_mask( 1u << PICO_DEFAULT_LED_PIN ); } - +#endif + void mcu_hw_main_loop(void) { TimerHandle_t led_timer_handle = xTimerCreate("LED timer", pdMS_TO_TICKS(200), pdTRUE, NULL, led_timer); diff --git a/src/rp2040/tusb_config.h b/src/rp2040/tusb_config.h index 2d2afb6..bc08866 100644 --- a/src/rp2040/tusb_config.h +++ b/src/rp2040/tusb_config.h @@ -34,13 +34,18 @@ // Board Specific Configuration //--------------------------------------------------------------------+ -#if CFG_TUSB_MCU == OPT_MCU_RP2040 +#ifdef WAVESHARE_RP2040_ZERO +// the waveshare rp2040 zero comes with a convenient USB-C connector +// and PIO USB would collide with PIO-WS2812, so this never uses PIO USB +#define CFG_TUH_RPI_PIO_USB 0 +#else // change to 0 if using on-board native micro USB // change to 1 if using pico-pio-usb as host controller for raspberry rp2040 #define CFG_TUH_RPI_PIO_USB 1 -#define BOARD_TUH_RHPORT CFG_TUH_RPI_PIO_USB #endif +#define BOARD_TUH_RHPORT CFG_TUH_RPI_PIO_USB + // RHPort number used for host can be defined by board.mk, default to port 0 #ifndef BOARD_TUH_RHPORT #define BOARD_TUH_RHPORT 0 diff --git a/src/rp2040/ws2812.pio.h b/src/rp2040/ws2812.pio.h new file mode 100644 index 0000000..3b2e25e --- /dev/null +++ b/src/rp2040/ws2812.pio.h @@ -0,0 +1,66 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +#pragma once + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +// ------ // +// ws2812 // +// ------ // + +#define ws2812_wrap_target 0 +#define ws2812_wrap 3 +#define ws2812_pio_version 0 + +#define ws2812_T1 2 +#define ws2812_T2 5 +#define ws2812_T3 3 + +static const uint16_t ws2812_program_instructions[] = { + // .wrap_target + 0x6221, // 0: out x, 1 side 0 [2] + 0x1123, // 1: jmp !x, 3 side 1 [1] + 0x1400, // 2: jmp 0 side 1 [4] + 0xa442, // 3: nop side 0 [4] + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program ws2812_program = { + .instructions = ws2812_program_instructions, + .length = 4, + .origin = -1, + // .pio_version = 0, +#if PICO_PIO_VERSION > 0 + .used_gpio_ranges = 0x0 +#endif +}; + +static inline pio_sm_config ws2812_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap); + sm_config_set_sideset(&c, 1, false, false); + return c; +} + +#include "hardware/clocks.h" +static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) { + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + pio_sm_config c = ws2812_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; + float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); + sm_config_set_clkdiv(&c, div); + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} + +#endif +