Skip to content

Commit

Permalink
Support for Waveshare RP2040-Zero
Browse files Browse the repository at this point in the history
  • Loading branch information
harbaum committed Aug 29, 2024
1 parent 63f5a52 commit a875500
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 25 deletions.
18 changes: 18 additions & 0 deletions src/rp2040/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Expand Down Expand Up @@ -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}
Expand Down
37 changes: 37 additions & 0 deletions src/rp2040/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
96 changes: 73 additions & 23 deletions src/rp2040/mcu_hw.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 ============== */
/* ============================================================================================= */
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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;
}

Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
9 changes: 7 additions & 2 deletions src/rp2040/tusb_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
66 changes: 66 additions & 0 deletions src/rp2040/ws2812.pio.h
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit a875500

Please sign in to comment.