Skip to content

Commit

Permalink
[Core] Add Raspberry Pi RP2040 support (#14877)
Browse files Browse the repository at this point in the history
* Disable RESET keycode because of naming conflicts

* Add Pico SDK as submodule

* Add RP2040 build support to QMK

* Adjust USB endpoint structs for RP2040

* Add RP2040 bootloader and double-tap reset routine

* Add generic and pro micro RP2040 boards

* Add RP2040 onekey keyboard

* Add WS2812 PIO DMA enabled driver and documentation

Supports regular and open-drain output configuration. RP2040 GPIOs are
sadly not 5V tolerant, so this is a bit use-less or needs extra hardware
or you take the risk to fry your hardware.

* Adjust SIO Driver for RP2040

* Adjust I2C Driver for RP2040

* Adjust SPI Driver for RP2040

* Add PIO serial driver and documentation

* Add general RP2040 documentation

* Apply suggestions from code review

Co-authored-by: Nick Brassel <nick@tzarc.org>

Co-authored-by: Nick Brassel <nick@tzarc.org>
  • Loading branch information
KarlK90 and tzarc authored Jun 30, 2022
1 parent d206c17 commit d717396
Show file tree
Hide file tree
Showing 43 changed files with 2,026 additions and 96 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@
[submodule "lib/printf"]
path = lib/printf
url = https://github.com/qmk/printf
[submodule "lib/pico-sdk"]
path = lib/pico-sdk
url = https://github.com/qmk/pico-sdk.git
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,7 @@ ifndef SKIP_GIT
if [ ! -e lib/lufa ]; then git submodule sync lib/lufa && git submodule update --depth 50 --init lib/lufa; fi
if [ ! -e lib/vusb ]; then git submodule sync lib/vusb && git submodule update --depth 50 --init lib/vusb; fi
if [ ! -e lib/printf ]; then git submodule sync lib/printf && git submodule update --depth 50 --init lib/printf; fi
if [ ! -e lib/pico-sdk ]; then git submodule sync lib/pico-sdk && git submodule update --depth 50 --init lib/pico-sdk; fi
git submodule status --recursive 2>/dev/null | \
while IFS= read -r x; do \
case "$$x" in \
Expand Down
4 changes: 4 additions & 0 deletions builddefs/bootloader.mk
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,10 @@ ifeq ($(strip $(BOOTLOADER)), tinyuf2)
OPT_DEFS += -DBOOTLOADER_TINYUF2
BOOTLOADER_TYPE = tinyuf2
endif
ifeq ($(strip $(BOOTLOADER)), rp2040)
OPT_DEFS += -DBOOTLOADER_RP2040
BOOTLOADER_TYPE = rp2040
endif
ifeq ($(strip $(BOOTLOADER)), halfkay)
OPT_DEFS += -DBOOTLOADER_HALFKAY
BOOTLOADER_TYPE = halfkay
Expand Down
4 changes: 2 additions & 2 deletions builddefs/common_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ ifeq ($(strip $(BACKLIGHT_ENABLE)), yes)
endif
endif

VALID_WS2812_DRIVER_TYPES := bitbang pwm spi i2c
VALID_WS2812_DRIVER_TYPES := bitbang pwm spi i2c vendor

WS2812_DRIVER ?= bitbang
ifeq ($(strip $(WS2812_DRIVER_REQUIRED)), yes)
Expand Down Expand Up @@ -637,7 +637,7 @@ ifneq ($(strip $(DEBOUNCE_TYPE)), custom)
endif


VALID_SERIAL_DRIVER_TYPES := bitbang usart
VALID_SERIAL_DRIVER_TYPES := bitbang usart vendor

SERIAL_DRIVER ?= bitbang
ifeq ($(filter $(SERIAL_DRIVER),$(VALID_SERIAL_DRIVER_TYPES)),)
Expand Down
35 changes: 35 additions & 0 deletions builddefs/mcu_selection.mk
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,41 @@ ifneq ($(findstring MK66FX1M0, $(MCU)),)
BOARD ?= PJRC_TEENSY_3_6
endif

ifneq ($(findstring RP2040, $(MCU)),)
# Cortex version
MCU = cortex-m0plus

# ARM version, CORTEX-M0/M1 are 6, CORTEX-M3/M4/M7 are 7
CHIBIOS_PORT = ARMv6-M-RP2

## chip/board settings
# - the next two should match the directories in
# <chibios[-contrib]>/os/hal/ports/$(MCU_PORT_NAME)/$(MCU_SERIES)
# OR
# <chibios[-contrib]>/os/hal/ports/$(MCU_FAMILY)/$(MCU_SERIES)
MCU_FAMILY = RP
MCU_SERIES = RP2040

# Linker script to use
# - it should exist either in <chibios>/os/common/ports/ARMCMx/compilers/GCC/ld/
# or <keyboard_dir>/ld/
STARTUPLD_CONTRIB = $(CHIBIOS_CONTRIB)/os/common/startup/ARMCMx/compilers/GCC/ld
MCU_LDSCRIPT ?= RP2040_FLASH
LDFLAGS += -L $(STARTUPLD_CONTRIB)

# Startup code to use
# - it should exist in <chibios>/os/common/startup/ARMCMx/compilers/GCC/mk/
MCU_STARTUP ?= rp2040

# Board: it should exist either in <chibios>/os/hal/boards/,
# <keyboard_dir>/boards/, or drivers/boards/
BOARD ?= GENERIC_PROMICRO_RP2040

# Default UF2 Bootloader settings
UF2_FAMILY ?= RP2040
FIRMWARE_FORMAT ?= uf2
endif

ifneq ($(findstring STM32F042, $(MCU)),)
# Cortex version
MCU = cortex-m0
Expand Down
4 changes: 4 additions & 0 deletions data/schemas/definitions.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@
"type": "string",
"pattern": "^LINE_PIN\\d{1,2}$"
},
{
"type": "string",
"pattern": "^GP\\d{1,2}$"
},
{
"type": "integer"
},
Expand Down
4 changes: 2 additions & 2 deletions data/schemas/keyboard.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
},
"processor": {
"type": "string",
"enum": ["cortex-m0", "cortex-m0plus", "cortex-m3", "cortex-m4", "MKL26Z64", "MK20DX128", "MK20DX256", "MK66FX1M0", "STM32F042", "STM32F072", "STM32F103", "STM32F303", "STM32F401", "STM32F405", "STM32F407", "STM32F411", "STM32F446", "STM32G431", "STM32G474", "STM32L412", "STM32L422", "STM32L432", "STM32L433", "STM32L442", "STM32L443", "GD32VF103", "WB32F3G71", "WB32FQ95", "atmega16u2", "atmega32u2", "atmega16u4", "atmega32u4", "at90usb162", "at90usb646", "at90usb647", "at90usb1286", "at90usb1287", "atmega32a", "atmega328p", "atmega328", "attiny85", "unknown"]
"enum": ["cortex-m0", "cortex-m0plus", "cortex-m3", "cortex-m4", "MKL26Z64", "MK20DX128", "MK20DX256", "MK66FX1M0", "RP2040", "STM32F042", "STM32F072", "STM32F103", "STM32F303", "STM32F401", "STM32F405", "STM32F407", "STM32F411", "STM32F446", "STM32G431", "STM32G474", "STM32L412", "STM32L422", "STM32L432", "STM32L433", "STM32L442", "STM32L443", "GD32VF103", "WB32F3G71", "WB32FQ95", "atmega16u2", "atmega32u2", "atmega16u4", "atmega32u4", "at90usb162", "at90usb646", "at90usb647", "at90usb1286", "at90usb1287", "atmega32a", "atmega328p", "atmega328", "attiny85", "unknown"]
},
"audio": {
"type": "object",
Expand Down Expand Up @@ -86,7 +86,7 @@
},
"bootloader": {
"type": "string",
"enum": ["atmel-dfu", "bootloadhid", "bootloadHID", "custom", "caterina", "halfkay", "kiibohd", "lufa-dfu", "lufa-ms", "md-boot", "qmk-dfu", "qmk-hid", "stm32-dfu", "stm32duino", "gd32v-dfu", "wb32-dfu", "unknown", "usbasploader", "USBasp", "tinyuf2"],
"enum": ["atmel-dfu", "bootloadhid", "bootloadHID", "custom", "caterina", "halfkay", "kiibohd", "lufa-dfu", "lufa-ms", "md-boot", "qmk-dfu", "qmk-hid", "stm32-dfu", "stm32duino", "gd32v-dfu", "wb32-dfu", "unknown", "usbasploader", "USBasp", "tinyuf2", "rp2040"],
},
"bootloader_instructions": {
"type": "string",
Expand Down
1 change: 1 addition & 0 deletions docs/_summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@
* Arm/ChibiOS
* [Selecting an MCU](platformdev_selecting_arm_mcu.md)
* [Early initialization](platformdev_chibios_earlyinit.md)
* [Raspberry Pi RP2040](platformdev_rp2040.md)

* QMK Reference
* [Contributing to QMK](contributing.md)
Expand Down
6 changes: 6 additions & 0 deletions docs/compatible_microcontrollers.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ You can also use any ARM chip with USB that [ChibiOS](https://www.chibios.org) s
* [MK66FX1M0](https://www.nxp.com/products/processors-and-microcontrollers/arm-microcontrollers/general-purpose-mcus/k-series-cortex-m4/k6x-ethernet/kinetis-k66-180-mhz-dual-high-speed-full-speed-usbs-2mb-flash-microcontrollers-mcus-based-on-arm-cortex-m4-core:K66_180)
* PJRC Teensy 3.6

### Raspberry Pi

* [RP2040](https://www.raspberrypi.com/documentation/microcontrollers/rp2040.html)

For a detailed overview about the RP2040 support by QMK see the [dedicated RP2040 page](platformdev_rp2040.md).

## Atmel ATSAM

There is limited support for one of Atmel's ATSAM microcontrollers, that being the [ATSAMD51J18A](https://www.microchip.com/wwwproducts/en/ATSAMD51J18A) used by the [Massdrop keyboards](https://github.com/qmk/qmk_firmware/tree/master/keyboards/massdrop). However, it is not recommended to design a board with this microcontroller as the support is quite specialized to Massdrop hardware.
Expand Down
39 changes: 39 additions & 0 deletions docs/flashing.md
Original file line number Diff line number Diff line change
Expand Up @@ -362,3 +362,42 @@ CLI Flashing sequence:
### `make` Targets

* `:uf2-split-left` and `:uf2-split-right`: Flashes the firmware but also sets the handedness setting in EEPROM by generating a side specific firmware.

## Raspberry Pi RP2040 UF2

The `rules.mk` setting for this bootloader is `rp2040`, and can be specified at the keymap or user level.

To ensure compatibility with the rp2040 bootloader, make sure this block is present in your `rules.mk`:

```make
# Bootloader selection
BOOTLOADER = rp2040
```

Compatible flashers:

* Any application able to copy a file from one place to another, such as _macOS Finder_ or _Windows Explorer_.

Flashing sequence:

1. Enter the bootloader using any of the following methods:
* Tap the `QK_BOOTLOADER` keycode
* Hold the `BOOTSEL` button on the PCB while plugin in the usb cable.
* Double-tap the `RESET` button on the PCB<sup>1</sup>.
2. Wait for the OS to detect the device
3. Copy the .uf2 file to the new USB disk
4. Wait for the keyboard to become available

or

CLI Flashing sequence:

1. Enter the bootloader using any of the following methods:
* Tap the `QK_BOOTLOADER` keycode
* Hold the `BOOTSEL` button on the PCB while plugin in the usb cable.
* Double-tap the `RESET` button on the PCB<sup>1</sup>.
2. Wait for the OS to detect the device
3. Flash via QMK CLI eg. `qmk flash --keyboard handwired/onekey/rpi_pico --keymap default`
4. Wait for the keyboard to become available

<sup>1</sup>: This works only if QMK was compiled with `RP2040_BOOTLOADER_DOUBLE_TAP_RESET` defined.
125 changes: 125 additions & 0 deletions docs/platformdev_rp2040.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# Raspberry Pi RP2040

The following table shows the current driver status for peripherals on RP2040 MCUs:

| System | Support |
| ------------------------------------ | ---------------------------------------------- |
| [ADC driver](adc_driver.md) | Support planned (no ETA) |
| [Audio](audio_driver.md) | Support planned (no ETA) |
| [I2C driver](i2c_driver.md) | :heavy_check_mark: |
| [SPI driver](spi_driver.md) | :heavy_check_mark: |
| [WS2812 driver](ws2812_driver.md) | :heavy_check_mark: using `PIO` driver |
| [External EEPROMs](eeprom_driver.md) | :heavy_check_mark: using `I2C` or `SPI` driver |
| [EEPROM emulation](eeprom_driver.md) | Support planned (no ETA) |
| [serial driver](serial_driver.md) | :heavy_check_mark: using `SIO` or `PIO` driver |
| [UART driver](uart_driver.md) | Support planned (no ETA) |

## GPIO

<img alt="Raspberry Pi Pico pinout" src="https://i.imgur.com/nLaiYDE.jpg" width="48%"/>
<img alt="Sparkfun RP2040 Pro Micro pinout" src="https://i.imgur.com/1TPAhrs.jpg" width="48%"/>

!> The GPIO pins of the RP2040 are not 5V tolerant!

### Pin nomenclature

To address individual pins on the RP2040, QMK uses the `GPx` abbreviation -- where the `x` stands for the GPIO number of the pin. This number can likely be found on the official pinout diagram of your board. Note that these GPIO numbers match the RP2040 MCU datasheet, and don't necessarily match the number you see printed on the board. For instance the Raspberry Pi Pico uses numbers from 1 to 40 for their pins, but these are not identical to the RP2040's GPIO numbers. So if you want to use the pin 11 of the Pico for your keyboard, you would refer to it as `GP8` in the config files.

### Alternate functions

The RP2040 features flexible GPIO function multiplexing, this means that every pin can be connected to nearly all the internal peripherals like I2C, SPI, UART or PWM. This allows for flexible PCB designs that are much less restricted in the selection of GPIO pins. To find out which pin can use which peripheral refer to the official [Raspberry PI RP2040 datasheet](https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#page=14) section 1.4.3 GPIO functions.

## Selecting hardware peripherals and drivers

QMK RP2040 support builds upon ChibiOS and thus follows their convention for activating drivers and associated hardware peripherals. These tables only give a quick overview which values have to be used, please refer to the ChibiOS specific sections on the driver pages.

### I2C Driver

| RP2040 Peripheral | `mcuconf.h` values | `I2C_DRIVER` |
| ----------------- | ------------------ | ------------ |
| `I2C0` | `RP_I2C_USE_I2C0` | `I2CD1` |
| `I2C1` | `RP_I2C_USE_I2C1` | `I2CD2` |

To configure the I2C driver please read the [ChibiOS/ARM](i2c_driver.md#arm-configuration) section.

### SPI Driver

| RP2040 Peripheral | `mcuconf.h` values | `SPI_DRIVER` |
| ----------------- | ------------------ | ------------ |
| `SPI0` | `RP_SPI_USE_SPI0` | `SPID0` |
| `SPI1` | `RP_SPI_USE_SPI1` | `SPID1` |

To configure the SPI driver please read the [ChibiOS/ARM](spi_driver.md#chibiosarm-configuration) section.

## Double-tap reset boot-loader entry :id=double-tap

The double-tap reset mechanism is an alternate way in QMK to enter the embedded mass storage UF2 boot-loader of the RP2040. It enables bootloader entry by a fast double-tap of the reset pin on start up, which is similar to the behavior of AVR Pro Micros. This feature activated by default for the Pro Micro RP2040 board, but has to be configured for other boards. To activate it, add the following options to your keyboards `config.h` file:

```c
#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET // Activates the double-tap behavior
#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_TIMEOUT 200U // Timeout window in ms in which the double tap can occur.
#define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_LED_MASK 0U // Specify a optional status led which blinks when entering the bootloader
```
## Pre-defined RP2040 boards
QMK defines two boards that you can choose from to base your RP2040 powered keyboard upon. These boards provide pre-configured default pins and drivers.
### Generic Pro Micro RP2040
This is the default board that is chosen, unless any other RP2040 board is selected in your keyboards `rules.mk` file. It assumes a pin layout for the I2C, SPI and Serial drivers which is identical to the Sparkfun Pro Micro RP2040, however all values can be overwritten by defining them in your keyboards `config.h` file. The [double-tap](#double-tap) reset to enter boot-loader behavior is activated by default.
| Driver configuration define | Value |
| -------------------------------------------------------------------------- | ------------------------------------ |
| **I2C driver** | |
| `I2C_DRIVER` | `I2CD2` |
| `I2C1_SDA_PIN` | `GP2` |
| `I2C1_SCL_PIN` | `GP3` |
| **SPI driver** | |
| `SPI_DRIVER` | `SPID0` |
| `SPI_SCK_PIN` | `GP18` |
| `SPI_MISO_PIN` | `GP20` |
| `SPI_MOSI_PIN` | `GP19` |
| **Serial driver** | |
| `SERIAL_USART_DRIVER` ([SIO Driver](serial_driver.md#the-sio-driver) only) | `SIOD0` |
| `SOFT_SERIAL_PIN` | undefined, use `SERIAL_USART_TX_PIN` |
| `SERIAL_USART_TX_PIN` | `GP0` |
| `SERIAL_USART_RX_PIN` | `GP1` |
?> The pin-outs of Adafruit's KB2040 and Boardsource's Blok both deviate from the Sparkfun Pro Micro RP2040. Lookup the pin-out of these boards and adjust your keyboards pin definition accordingly if you want to use these boards.
### Generic RP2040 board
This board can be chosen as a base for RP2040 keyboards which configure all necessary pins and drivers themselves and do not wish to leverage the configuration matching the Generic Pro Micro RP2040 board. Thus it doesn't provide any pre-configured pins or drivers. To select this board add the following line to your keyboards `rules.mk` file.
```make
BOARD = GENERIC_RP_RP2040
```

## Split keyboard support

Split keyboards are fully supported using the [serial driver](serial_driver.md) in both full-duplex and half-duplex configurations. Two driver subsystems are supported by the RP2040, the hardware UART based `SIO` and the Programmable IO based `PIO` driver.

| Feature | [SIO Driver](serial_driver.md#the-sio-driver) | [PIO Driver](serial_driver.md#the-pio-driver) |
| ----------------------------- | --------------------------------------------- | --------------------------------------------- |
| Half-Duplex operation | | :heavy_check_mark: |
| Full-Duplex operation | :heavy_check_mark: | :heavy_check_mark: |
| `TX` and `RX` pin swapping | | :heavy_check_mark: |
| Any GPIO as `TX` and `RX` pin | Only UART capable pins | :heavy_check_mark: |
| Simple configuration | | :heavy_check_mark: |

The `PIO` driver is much more flexible then the `SIO` driver, the only "downside" is the usage of `PIO` resources which in turn are not available for advanced user programs. Under normal circumstances, this resource allocation will be a non-issue.

## RP2040 second stage bootloader selection

As the RP2040 does not have any internal flash memory it depends on an external SPI flash memory chip to store and execute instructions from. To successfully interact with a wide variety of these chips a second stage bootloader that is compatible with the chosen external flash memory has to be supplied with each firmware image. By default an `W25Q080` compatible bootloader is assumed, but others can be chosen by adding one of the defines listed in the table below to your keyboards `config.h` file.

| Compatible with flash chip | Selection |
| :------------------------- | ---------------------------------- |
| W25Q080 | Selected by default |
| AT25SF128A | `#define RP2040_FLASH_AT25SF128A` |
| GD25Q64CS | `#define RP2040_FLASH_GD25Q64CS` |
| W25X10CL | `#define RP2040_FLASH_W25X10CL` |
| IS25LP080 | `#define RP2040_FLASH_IS25LP080` |
| Generic 03H flash | `#define RP2040_FLASH_GENERIC_03H` |
Loading

0 comments on commit d717396

Please sign in to comment.