├── .gitignore ├── CMakeLists.txt ├── Doxyfile.in ├── LICENSE ├── README.md ├── examples ├── CMakeLists.txt ├── pico_sdk_import.cmake └── psram-test.c ├── psram_spi.c ├── psram_spi.h └── psram_spi.pio /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *.swp 4 | *.swo 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Generated Cmake Pico project file 2 | 3 | cmake_minimum_required(VERSION 3.13) 4 | 5 | set(CMAKE_C_STANDARD 11) 6 | 7 | project(rp2040-psram 8 | VERSION 1.0.0 9 | DESCRIPTION "A header-only library to allow access to SPI PSRAM via PIO on the RP2040 microcontroller." 10 | ) 11 | 12 | add_library(rp2040-psram INTERFACE) 13 | 14 | target_sources(rp2040-psram INTERFACE 15 | psram_spi.c 16 | ) 17 | target_include_directories(rp2040-psram INTERFACE ${CMAKE_CURRENT_LIST_DIR}) 18 | 19 | if(PICO_SDK) 20 | pico_generate_pio_header(rp2040-psram ${CMAKE_CURRENT_LIST_DIR}/psram_spi.pio) 21 | endif(PICO_SDK) 22 | 23 | option(BUILD_PSRAM_DOCS "Build documentation" OFF) 24 | 25 | if(BUILD_PSRAM_DOCS) 26 | # check if Doxygen is installed 27 | find_package(Doxygen) 28 | if (DOXYGEN_FOUND) 29 | # set input and output files 30 | set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in) 31 | set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 32 | 33 | # request to configure the file 34 | configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) 35 | message("Doxygen build started") 36 | 37 | # note the option ALL which allows to build the docs together with the application 38 | add_custom_target( doc_doxygen ALL 39 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} 40 | WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} 41 | COMMENT "Generating API documentation with Doxygen" 42 | VERBATIM ) 43 | else (DOXYGEN_FOUND) 44 | message("Doxygen need to be installed to generate the doxygen documentation") 45 | endif (DOXYGEN_FOUND) 46 | endif(BUILD_PSRAM_DOCS) 47 | -------------------------------------------------------------------------------- /Doxyfile.in: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = "@CMAKE_PROJECT_NAME@" 2 | PROJECT_BRIEF = "@CMAKE_PROJECT_DESCRIPTION@" 3 | PROJECT_NUMBER = "@CMAKE_PROJECT_VERSION@" 4 | GENERATE_LATEX = NO 5 | OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/docs/ 6 | INPUT = @CMAKE_CURRENT_SOURCE_DIR@ 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright © 2023 Ian Scott 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the “Software”), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rp2040-psram 2 | 3 | A header-only C library to allow access to SPI PSRAM via PIO on the RP2040 microcontroller as used on the Raspberry Pi Pico. Due to the timing requirements of these PSRAM devices, reading data from them at high speed (>84MHz) requires the following clock behavior: 4 | 5 | - Read data needs to be sampled on the clock falling edge. 6 | - An extra "fudge factor" clock cycle is required before reading data. 7 | 8 | I could not get the hardware SPI interface on the RP2040 to work at high speed with this behavior, so I created a PIO SPI implementation specifically tailored to the behavior of the PSRAM chips and optimized for the highest speed possible: 9 | 10 | - A read or write command is given, followed by an optional read of data. 11 | - Chip select is driven by PIO. 12 | - DMA is used so CPU cycles are not used to service the PIO TX and RX FIFOs. 13 | - Special optimized functions are provided for writing 8, 16, and 32 bit data as fast as possible. 14 | - All functions are tagged with `__force_inline` for the fastest speed possible. 15 | 16 | Tested PSRAM chips: 17 | 18 | - apmemory APS6404L 19 | - Lyontek LY68L6400 20 | - IPUS IPS6404 21 | - Espressif ESP-PSRAM64H 22 | 23 | These chips have been tested as stable up to an RP2040 clock speed of 280MHz, creating an effecitve SPI clock speed of 140MHz. 24 | 25 | ## Examples 26 | 27 | An example that tests the PSRAM by writing and reading 8MB of data with 8, 16, 32, and 128 bit access sizes is included in the `examples/` directory. 28 | 29 | ## Using this library 30 | 31 | ### Hardware connections 32 | 33 | Due to using sideset to drive the CS and SCK signals, those two pins must be on consecutive GPIOs. For example, if CS is on GPIO 0, SCK must be on GPIO 1. MOSI and MISO may be on any pin and do not need to be adjacent. 34 | 35 | QSPI support is on the roadmap for the future. If you plan on using QSPI, SIO[0] through SIO[3] must be on consecutive GPIOs. 36 | 37 | ### Including in the build 38 | 39 | Clone this repository and copy to a subdirectory or add as a submodule in your project. In your project's `CMakeLists.txt` file, add the following: 40 | 41 | ``` 42 | add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/rp2040-psram rp2040-psram) 43 | ``` 44 | 45 | And then add `rp2040-psram` to your project's `target_link_libraries`. 46 | 47 | ### Using in your code 48 | 49 | Include `psram_spi.h` in your file. See the [documentation for this file](https://polpo.github.io/rp2040-psram/psram__spi_8h.html) for how to initialize and use this library. 50 | 51 | The following defines _MUST_ be defined: 52 | 53 | - `PSRAM_PIN_CS` - GPIO number of the chip select pin 54 | - `PSRAM_PIN_SCK` - GPIO number of the clock pin 55 | - `PSRAM_PIN_MOSI` - GPIO number of the MOSI pin 56 | - `PSRAM_PIN_MISO` - GPIO number of the MISO pin 57 | 58 | Optional define: 59 | 60 | - `PSRAM_MUTEX` - Define this to put PSRAM access behind a mutex. This must be used if the PSRAM is to be used by multiple cores. 61 | 62 | 63 | ## Projects that use rp2040-psram 64 | 65 | [PicoGUS](https://github.com/polpo/picogus) - uses PSRAM to emulate the Gravis Ultrasound's 1MB sample RAM 66 | 67 | # License 68 | 69 | rp2040-psram is licensed under the MIT license (see LICENSE) 70 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(CMAKE_C_STANDARD 11) 4 | 5 | # Initialise pico_sdk from installed location 6 | # (note this can come from environment, CMake cache etc) 7 | set(PICO_SDK_PATH "${CMAKE_SOURCE_DIR}/../../pico/pico-sdk") 8 | 9 | # Pull in Raspberry Pi Pico SDK (must be before project) 10 | include(pico_sdk_import.cmake) 11 | 12 | project(psram-test C CXX ASM) 13 | 14 | set(CMAKE_BUILD_TYPE "Release") 15 | set(PICO_COPY_TO_RAM 1) 16 | 17 | # Initialise the Raspberry Pi Pico SDK 18 | pico_sdk_init() 19 | 20 | add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/.. rp2040-psram) 21 | 22 | # Add executable. Default name is the project name, version 0.1 23 | add_executable(psram-test psram-test.c) 24 | pico_set_program_version(psram-test "1.0.0") 25 | 26 | add_compile_definitions( 27 | PICO_DEFAULT_UART=0 28 | PICO_DEFAULT_UART_TX_PIN=0 29 | PICO_DEFAULT_UART_RX_PIN=-1 30 | ) 31 | 32 | target_compile_definitions(psram-test PRIVATE 33 | # PSRAM_MUTEX=1 34 | PSRAM_SPINLOCK=1 35 | PSRAM_ASYNC=1 36 | PSRAM_PIN_CS=9 37 | PSRAM_PIN_SCK=10 38 | PSRAM_PIN_MOSI=11 39 | PSRAM_PIN_MISO=12 40 | ) 41 | 42 | target_link_libraries( 43 | psram-test 44 | rp2040-psram 45 | pico_stdlib 46 | hardware_pio 47 | hardware_dma 48 | hardware_pll 49 | ) 50 | 51 | pico_add_extra_outputs(psram-test) 52 | 53 | add_custom_target( 54 | program 55 | DEPENDS psram-test 56 | COMMAND openocd -f interface/picoprobe.cfg -f target/rp2040.cfg -c "program ${CMAKE_CURRENT_BINARY_DIR}/psram-test.elf verify reset exit" 57 | ) 58 | -------------------------------------------------------------------------------- /examples/pico_sdk_import.cmake: -------------------------------------------------------------------------------- 1 | # This is a copy of /external/pico_sdk_import.cmake 2 | 3 | # This can be dropped into an external project to help locate this SDK 4 | # It should be include()ed prior to project() 5 | 6 | if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) 7 | set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) 8 | message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") 9 | endif () 10 | 11 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) 12 | set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) 13 | message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") 14 | endif () 15 | 16 | if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) 17 | set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) 18 | message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") 19 | endif () 20 | 21 | set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") 22 | set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") 23 | set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") 24 | 25 | if (NOT PICO_SDK_PATH) 26 | if (PICO_SDK_FETCH_FROM_GIT) 27 | include(FetchContent) 28 | set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) 29 | if (PICO_SDK_FETCH_FROM_GIT_PATH) 30 | get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") 31 | endif () 32 | # GIT_SUBMODULES_RECURSE was added in 3.17 33 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0") 34 | FetchContent_Declare( 35 | pico_sdk 36 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 37 | GIT_TAG master 38 | GIT_SUBMODULES_RECURSE FALSE 39 | ) 40 | else () 41 | FetchContent_Declare( 42 | pico_sdk 43 | GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk 44 | GIT_TAG master 45 | ) 46 | endif () 47 | 48 | if (NOT pico_sdk) 49 | message("Downloading Raspberry Pi Pico SDK") 50 | FetchContent_Populate(pico_sdk) 51 | set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) 52 | endif () 53 | set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) 54 | else () 55 | message(FATAL_ERROR 56 | "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." 57 | ) 58 | endif () 59 | endif () 60 | 61 | get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") 62 | if (NOT EXISTS ${PICO_SDK_PATH}) 63 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") 64 | endif () 65 | 66 | set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) 67 | if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) 68 | message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") 69 | endif () 70 | 71 | set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) 72 | 73 | include(${PICO_SDK_INIT_CMAKE_FILE}) 74 | -------------------------------------------------------------------------------- /examples/psram-test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include "psram_spi.h" 7 | 8 | psram_spi_inst_t* async_spi_inst; 9 | 10 | int main() 11 | { 12 | // Overclock! 13 | set_sys_clock_khz(280000, true); 14 | 15 | stdio_init_all(); 16 | 17 | puts("PSRAM test - rp2040-psram v1.0.0"); 18 | 19 | puts("Initing PSRAM..."); 20 | psram_spi_inst_t psram_spi = psram_spi_init(pio0, -1); 21 | 22 | uint32_t psram_begin, psram_elapsed; 23 | float psram_speed; 24 | 25 | puts("Testing PSRAM..."); 26 | 27 | // **************** 8 bits testing **************** 28 | psram_begin = time_us_32(); 29 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); ++addr) { 30 | psram_write8(&psram_spi, addr, (addr & 0xFF)); 31 | } 32 | psram_elapsed = time_us_32() - psram_begin; 33 | psram_speed = 1000000.0 * 8 * 1024.0 * 1024 / psram_elapsed; 34 | printf("8 bit: PSRAM write 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 35 | 36 | psram_begin = time_us_32(); 37 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); ++addr) { 38 | psram_write8_async(&psram_spi, addr, (addr & 0xFF)); 39 | } 40 | psram_elapsed = time_us_32() - psram_begin; 41 | psram_speed = 1000000.0 * 8 * 1024.0 * 1024 / psram_elapsed; 42 | printf("8 bit: PSRAM write async 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 43 | 44 | psram_begin = time_us_32(); 45 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); ++addr) { 46 | uint8_t result = psram_read8(&psram_spi, addr); 47 | if ((uint8_t)(addr & 0xFF) != result) { 48 | printf("\nPSRAM failure at address %x (%x != %x)\n", addr, addr & 0xFF, result); 49 | return 1; 50 | } 51 | } 52 | psram_elapsed = time_us_32() - psram_begin; 53 | psram_speed = 1000000.0 * 8 * 1024.0 * 1024 / psram_elapsed; 54 | printf("8 bit: PSRAM read 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 55 | 56 | // **************** 16 bits testing **************** 57 | psram_begin = time_us_32(); 58 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); addr += 2) { 59 | psram_write16(&psram_spi, addr, (((addr + 1) & 0xFF) << 8) | (addr & 0xFF)); 60 | } 61 | psram_elapsed = time_us_32() - psram_begin; 62 | psram_speed = 1000000.0 * 8 * 1024.0 * 1024 / psram_elapsed; 63 | printf("16 bit: PSRAM write 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 64 | 65 | psram_begin = time_us_32(); 66 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); addr += 2) { 67 | uint16_t result = psram_read16(&psram_spi, addr); 68 | if ((uint16_t)( 69 | (((addr + 1) & 0xFF) << 8) | 70 | (addr & 0xFF)) != result 71 | ) { 72 | printf("PSRAM failure at address %x (%x != %x) ", addr, ( 73 | (((addr + 1) & 0xFF) << 8) | 74 | (addr & 0xFF)), result 75 | ); 76 | return 1; 77 | } 78 | } 79 | psram_elapsed = (time_us_32() - psram_begin); 80 | psram_speed = 1000000.0 * 8 * 1024 * 1024 / psram_elapsed; 81 | printf("16 bit: PSRAM read 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 82 | 83 | // **************** 32 bits testing **************** 84 | psram_begin = time_us_32(); 85 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); addr += 4) { 86 | psram_write32( 87 | &psram_spi, addr, 88 | (uint32_t)( 89 | (((addr + 3) & 0xFF) << 24) | 90 | (((addr + 2) & 0xFF) << 16) | 91 | (((addr + 1) & 0xFF) << 8) | 92 | (addr & 0XFF)) 93 | ); 94 | } 95 | psram_elapsed = time_us_32() - psram_begin; 96 | psram_speed = 1000000.0 * 8 * 1024.0 * 1024 / psram_elapsed; 97 | printf("32 bit: PSRAM write 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 98 | 99 | psram_begin = time_us_32(); 100 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); addr += 4) { 101 | uint32_t result = psram_read32(&psram_spi, addr); 102 | if ((uint32_t)( 103 | (((addr + 3) & 0xFF) << 24) | 104 | (((addr + 2) & 0xFF) << 16) | 105 | (((addr + 1) & 0xFF) << 8) | 106 | (addr & 0XFF)) != result 107 | ) { 108 | printf("PSRAM failure at address %x (%x != %x) ", addr, ( 109 | (((addr + 3) & 0xFF) << 24) | 110 | (((addr + 2) & 0xFF) << 16) | 111 | (((addr + 1) & 0xFF) << 8) | 112 | (addr & 0XFF)), result 113 | ); 114 | return 1; 115 | } 116 | } 117 | psram_elapsed = (time_us_32() - psram_begin); 118 | psram_speed = 1000000.0 * 8 * 1024 * 1024 / psram_elapsed; 119 | printf("32 bit: PSRAM read 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 120 | 121 | // **************** n bits testing **************** 122 | uint8_t write_data[256]; 123 | for (size_t i = 0; i < 256; ++i) { 124 | write_data[i] = i; 125 | } 126 | psram_begin = time_us_32(); 127 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); addr += 256) { 128 | for (uint32_t step = 0; step < 256; step += 16) { 129 | psram_write(&psram_spi, addr + step, write_data + step, 16); 130 | } 131 | } 132 | psram_elapsed = time_us_32() - psram_begin; 133 | psram_speed = 1000000.0 * 8 * 1024.0 * 1024 / psram_elapsed; 134 | printf("128 bit: PSRAM write 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 135 | 136 | psram_begin = time_us_32(); 137 | uint8_t read_data[16]; 138 | for (uint32_t addr = 0; addr < (8 * 1024 * 1024); addr += 256) { 139 | for (uint32_t step = 0; step < 256; step += 16) { 140 | psram_read(&psram_spi, addr + step, read_data, 16); 141 | if (memcmp(read_data, write_data + step, 16) != 0) { 142 | printf("PSRAM failure at address %x", addr); 143 | return 1; 144 | } 145 | } 146 | } 147 | psram_elapsed = time_us_32() - psram_begin; 148 | psram_speed = 1000000.0 * 8 * 1024.0 * 1024 / psram_elapsed; 149 | printf("128 bit: PSRAM read 8MB in %d us, %d B/s\n", psram_elapsed, (uint32_t)psram_speed); 150 | 151 | 152 | } 153 | -------------------------------------------------------------------------------- /psram_spi.c: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | 3 | rp2040-psram 4 | 5 | Copyright © 2023 Ian Scott 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the “Software”), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | ******************************************************************************/ 26 | #include "psram_spi.h" 27 | 28 | #include 29 | 30 | #if defined(PSRAM_ASYNC) && defined(PSRAM_ASYNC_SYNCHRONIZE) 31 | void __isr psram_dma_complete_handler() { 32 | #if PSRAM_ASYNC_DMA_IRQ == 0 33 | dma_hw->ints0 = 1u << async_spi_inst->async_dma_chan; 34 | #elif PSRAM_ASYNC_DMA_IRQ == 1 35 | dma_hw->ints1 = 1u << async_spi_inst->async_dma_chan; 36 | #else 37 | #error "PSRAM_ASYNC defined without PSRAM_ASYNC_DMA_IRQ set to 0 or 1" 38 | #endif 39 | /* putchar('@'); */ 40 | #if defined(PSRAM_MUTEX) 41 | mutex_exit(&async_spi_inst->mtx); 42 | #elif defined(PSRAM_SPINLOCK) 43 | spin_unlock(async_spi_inst->spinlock, async_spi_inst->spin_irq_state); 44 | #endif 45 | } 46 | #endif // defined(PSRAM_ASYNC) && defined(PSRAM_ASYNC_SYNCHRONIZE) 47 | 48 | psram_spi_inst_t psram_spi_init_clkdiv(PIO pio, int sm, float clkdiv, bool fudge) { 49 | psram_spi_inst_t spi; 50 | spi.pio = pio; 51 | spi.offset = pio_add_program(spi.pio, fudge ? &spi_psram_fudge_program : &spi_psram_program); 52 | if (sm == -1) { 53 | spi.sm = pio_claim_unused_sm(spi.pio, true); 54 | } else { 55 | spi.sm = sm; 56 | } 57 | #if defined(PSRAM_MUTEX) 58 | mutex_init(&spi.mtx); 59 | #elif defined(PSRAM_SPINLOCK) 60 | int spin_id = spin_lock_claim_unused(true); 61 | spi.spinlock = spin_lock_init(spin_id); 62 | #endif 63 | 64 | gpio_set_drive_strength(PSRAM_PIN_CS, GPIO_DRIVE_STRENGTH_4MA); 65 | gpio_set_drive_strength(PSRAM_PIN_SCK, GPIO_DRIVE_STRENGTH_4MA); 66 | gpio_set_drive_strength(PSRAM_PIN_MOSI, GPIO_DRIVE_STRENGTH_4MA); 67 | /* gpio_set_slew_rate(PSRAM_PIN_CS, GPIO_SLEW_RATE_FAST); */ 68 | /* gpio_set_slew_rate(PSRAM_PIN_SCK, GPIO_SLEW_RATE_FAST); */ 69 | /* gpio_set_slew_rate(PSRAM_PIN_MOSI, GPIO_SLEW_RATE_FAST); */ 70 | 71 | pio_spi_psram_cs_init(spi.pio, spi.sm, spi.offset, 8 /*n_bits*/, clkdiv, fudge, PSRAM_PIN_CS, PSRAM_PIN_MOSI, PSRAM_PIN_MISO); 72 | 73 | // Write DMA channel setup 74 | spi.write_dma_chan = dma_claim_unused_channel(true); 75 | spi.write_dma_chan_config = dma_channel_get_default_config(spi.write_dma_chan); 76 | channel_config_set_transfer_data_size(&spi.write_dma_chan_config, DMA_SIZE_8); 77 | channel_config_set_read_increment(&spi.write_dma_chan_config, true); 78 | channel_config_set_write_increment(&spi.write_dma_chan_config, false); 79 | channel_config_set_dreq(&spi.write_dma_chan_config, pio_get_dreq(spi.pio, spi.sm, true)); 80 | dma_channel_set_write_addr(spi.write_dma_chan, &spi.pio->txf[spi.sm], false); 81 | dma_channel_set_config(spi.write_dma_chan, &spi.write_dma_chan_config, false); 82 | 83 | // Read DMA channel setup 84 | spi.read_dma_chan = dma_claim_unused_channel(true); 85 | spi.read_dma_chan_config = dma_channel_get_default_config(spi.read_dma_chan); 86 | channel_config_set_transfer_data_size(&spi.read_dma_chan_config, DMA_SIZE_8); 87 | channel_config_set_read_increment(&spi.read_dma_chan_config, false); 88 | channel_config_set_write_increment(&spi.read_dma_chan_config, true); 89 | channel_config_set_dreq(&spi.read_dma_chan_config, pio_get_dreq(spi.pio, spi.sm, false)); 90 | dma_channel_set_read_addr(spi.read_dma_chan, &spi.pio->rxf[spi.sm], false); 91 | dma_channel_set_config(spi.read_dma_chan, &spi.read_dma_chan_config, false); 92 | 93 | #if defined(PSRAM_ASYNC) 94 | // Asynchronous DMA channel setup 95 | spi.async_dma_chan = dma_claim_unused_channel(true); 96 | spi.async_dma_chan_config = dma_channel_get_default_config(spi.async_dma_chan); 97 | channel_config_set_transfer_data_size(&spi.async_dma_chan_config, DMA_SIZE_8); 98 | channel_config_set_read_increment(&spi.async_dma_chan_config, true); 99 | channel_config_set_write_increment(&spi.async_dma_chan_config, false); 100 | channel_config_set_dreq(&spi.async_dma_chan_config, pio_get_dreq(spi.pio, spi.sm, true)); 101 | dma_channel_set_write_addr(spi.async_dma_chan, &spi.pio->txf[spi.sm], false); 102 | dma_channel_set_config(spi.async_dma_chan, &spi.async_dma_chan_config, false); 103 | 104 | #if defined(PSRAM_ASYNC_COMPLETE) 105 | irq_set_exclusive_handler(DMA_IRQ_0 + PSRAM_ASYNC_DMA_IRQ, psram_dma_complete_handler); 106 | dma_irqn_set_channel_enabled(PSRAM_ASYNC_DMA_IRQ, spi.async_dma_chan, true); 107 | irq_set_enabled(DMA_IRQ_0 + PSRAM_ASYNC_DMA_IRQ, true); 108 | #endif // defined(PSRAM_ASYNC_COMPLETE) 109 | #endif // defined(PSRAM_ASYNC) 110 | 111 | uint8_t psram_reset_en_cmd[] = { 112 | 8, // 8 bits to write 113 | 0, // 0 bits to read 114 | 0x66u // Reset enable command 115 | }; 116 | pio_spi_write_read_dma_blocking(&spi, psram_reset_en_cmd, 3, 0, 0); 117 | busy_wait_us(50); 118 | uint8_t psram_reset_cmd[] = { 119 | 8, // 8 bits to write 120 | 0, // 0 bits to read 121 | 0x99u // Reset command 122 | }; 123 | pio_spi_write_read_dma_blocking(&spi, psram_reset_cmd, 3, 0, 0); 124 | busy_wait_us(100); 125 | 126 | return spi; 127 | }; 128 | 129 | psram_spi_inst_t psram_spi_init(PIO pio, int sm) { 130 | return psram_spi_init_clkdiv(pio, sm, 1.0, true); 131 | } 132 | 133 | void psram_spi_uninit(psram_spi_inst_t spi, bool fudge) { 134 | #if defined(PSRAM_ASYNC) 135 | // Asynchronous DMA channel teardown 136 | dma_channel_unclaim(spi.async_dma_chan); 137 | #if defined(PSRAM_ASYNC_COMPLETE) 138 | irq_set_enabled(DMA_IRQ_0 + PSRAM_ASYNC_DMA_IRQ, false); 139 | dma_irqn_set_channel_enabled(PSRAM_ASYNC_DMA_IRQ, spi.async_dma_chan, false); 140 | irq_remove_handler(DMA_IRQ_0 + PSRAM_ASYNC_DMA_IRQ, psram_dma_complete_handler); 141 | #endif // defined(PSRAM_ASYNC_COMPLETE) 142 | #endif // defined(PSRAM_ASYNC) 143 | 144 | // Write DMA channel teardown 145 | dma_channel_unclaim(spi.write_dma_chan); 146 | 147 | // Read DMA channel teardown 148 | dma_channel_unclaim(spi.read_dma_chan); 149 | 150 | #if defined(PSRAM_SPINLOCK) 151 | int spin_id = spin_lock_get_num(spi.spinlock); 152 | spin_lock_unclaim(spin_id); 153 | #endif 154 | 155 | pio_sm_unclaim(spi.pio, spi.sm); 156 | pio_remove_program(spi.pio, fudge ? &spi_psram_fudge_program : &spi_psram_program, spi.offset); 157 | } 158 | 159 | int test_psram(psram_spi_inst_t* psram_spi, int increment) { 160 | puts("Writing PSRAM..."); 161 | uint8_t deadbeef[8] = {0xd, 0xe, 0xa, 0xd, 0xb, 0xe, 0xe, 0xf}; 162 | /* uncomment to write 8 bits at a time. the below for loop for 32-bit writes is much faster 163 | for (uint32_t addr = 0; addr < (1024 * 1024); ++addr) { 164 | psram_write8(psram_spi, addr, (addr & 0xFF)); 165 | // psram_write8_async(psram_spi, addr, (addr & 0xFF)); 166 | } 167 | */ 168 | for (uint32_t addr = 0; addr < (1024 * 1024); addr += 4) { 169 | uint32_t value = (uint32_t)( 170 | (((addr + 3) & 0xFF) << 24) | 171 | (((addr + 2) & 0xFF) << 16) | 172 | (((addr + 1) & 0xFF) << 8) | 173 | (addr & 0XFF)); 174 | psram_write32(psram_spi, addr, value); 175 | } 176 | puts("Reading PSRAM..."); 177 | uint32_t psram_begin = time_us_32(); 178 | for (uint32_t addr = 0; addr < (1024 * 1024); addr += increment) { 179 | uint8_t result = psram_read8(psram_spi, addr); 180 | uint8_t test = (uint8_t)(addr & 0xFF); 181 | if (test != result) { 182 | printf("\nPSRAM failure at address %x (%x != %x)\n", addr, test, result); 183 | return 1; 184 | } 185 | } 186 | uint32_t psram_elapsed = time_us_32() - psram_begin; 187 | float psram_speed = 1000000.0 * 1024.0 * 1024 / psram_elapsed / increment; 188 | printf("8 bit: PSRAM read in %d us, %d B/s (target 705600 B/s)\n", psram_elapsed, (uint32_t)psram_speed); 189 | 190 | psram_begin = time_us_32(); 191 | for (uint32_t addr = 0; addr < (1024 * 1024); addr += (2 * increment)) { 192 | uint16_t result = psram_read16(psram_spi, addr); 193 | uint16_t test = (uint16_t)( 194 | (((addr + 1) & 0xFF) << 8) | 195 | (addr & 0XFF)); 196 | if (test != result 197 | ) { 198 | printf("PSRAM failure at address %x (%x != %x) ", addr, test, result); 199 | return 1; 200 | } 201 | } 202 | psram_elapsed = (time_us_32() - psram_begin); 203 | psram_speed = 1000000.0 * 1024 * 1024 / psram_elapsed / increment; 204 | printf("16 bit: PSRAM read in %d us, %d B/s (target 1411200 B/s)\n", psram_elapsed, (uint32_t)psram_speed); 205 | 206 | psram_begin = time_us_32(); 207 | for (uint32_t addr = 0; addr < (1024 * 1024); addr += (4 * increment)) { 208 | uint32_t result = psram_read32(psram_spi, addr); 209 | uint32_t test = (uint32_t)( 210 | (((addr + 3) & 0xFF) << 24) | 211 | (((addr + 2) & 0xFF) << 16) | 212 | (((addr + 1) & 0xFF) << 8) | 213 | (addr & 0XFF)); 214 | if (test != result 215 | ) { 216 | printf("PSRAM failure at address %x (%x != %x) ", addr, test, result); 217 | return 1; 218 | } 219 | } 220 | psram_elapsed = (time_us_32() - psram_begin); 221 | psram_speed = 1000000.0 * 1024 * 1024 / psram_elapsed / increment; 222 | printf("32 bit: PSRAM read in %d us, %d B/s (target 1411200 B/s)\n", psram_elapsed, (uint32_t)psram_speed); 223 | return 0; 224 | } 225 | -------------------------------------------------------------------------------- /psram_spi.h: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | 3 | rp2040-psram 4 | 5 | Copyright © 2023 Ian Scott 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the “Software”), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | ******************************************************************************/ 26 | 27 | /** 28 | * @file psram_spi.h 29 | * 30 | * \mainpage 31 | * 32 | * The interface to this file is defined in psram_spi.h. Please see the 33 | * documentation for this file. 34 | * 35 | * The following defines _MUST_ be defined: 36 | * 37 | * - @c PSRAM_PIN_CS - GPIO number of the chip select pin 38 | * - @c PSRAM_PIN_SCK - GPIO number of the clock pin 39 | * - @c PSRAM_PIN_MOSI - GPIO number of the MOSI pin 40 | * - @c PSRAM_PIN_MISO - GPIO number of the MISO pin 41 | * 42 | * Optional define: 43 | * - @c PSRAM_MUTEX - Define this to put PSRAM access behind a mutex. This must 44 | * be used if the PSRAM is to be used by multiple cores. 45 | * 46 | * Project homepage: https://github.com/polpo/rp2040-psram 47 | */ 48 | 49 | #pragma once 50 | 51 | #include "hardware/pio.h" 52 | #include "hardware/gpio.h" 53 | #include "hardware/timer.h" 54 | #include "hardware/dma.h" 55 | #if defined(PSRAM_MUTEX) 56 | #include "pico/mutex.h" 57 | #elif defined(PSRAM_SPINLOCK) 58 | #include "hardware/sync.h" 59 | #endif 60 | #include 61 | 62 | #include "psram_spi.pio.h" 63 | 64 | #ifdef __cplusplus 65 | extern "C" { 66 | #endif 67 | 68 | /** 69 | * @brief A struct that holds the configuration for the PSRAM interface. 70 | * 71 | * This struct is generated by psram_spi_init() and must be passed to all calls to 72 | * the psram access functions. 73 | */ 74 | typedef struct psram_spi_inst { 75 | PIO pio; 76 | int sm; 77 | uint offset; 78 | #if defined(PSRAM_MUTEX) 79 | mutex_t mtx; 80 | #elif defined(PSRAM_SPINLOCK) 81 | spin_lock_t* spinlock; 82 | uint32_t spin_irq_state; 83 | #endif 84 | int write_dma_chan; 85 | dma_channel_config write_dma_chan_config; 86 | int read_dma_chan; 87 | dma_channel_config read_dma_chan_config; 88 | #if defined(PSRAM_ASYNC) 89 | int async_dma_chan; 90 | dma_channel_config async_dma_chan_config; 91 | #endif 92 | } psram_spi_inst_t; 93 | 94 | #if defined(PSRAM_ASYNC) 95 | extern psram_spi_inst_t* async_spi_inst; 96 | #endif 97 | 98 | /** 99 | * @brief Write and read raw data to the PSRAM SPI PIO, driven by the CPU 100 | * without DMA. This can be used if DMA has not yet been initialized. 101 | * 102 | * Used to send raw commands and receive data from the PSRAM. Usually the @c 103 | * psram_write*() and @c psram_read*() commands should be used instead. 104 | * 105 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 106 | * @param src Pointer to the source data to write. 107 | * @param src_len Length of the source data in bytes. 108 | * @param dst Pointer to the destination for read data, if any. Set to 0 or NULL 109 | * if no data is to be read. 110 | * @param dst_len Length of the destination data in bytes. Set to 0 if no data 111 | * is to be read. 112 | */ 113 | __force_inline static void __time_critical_func(pio_spi_write_read_blocking)( 114 | psram_spi_inst_t* spi, 115 | const uint8_t* src, const size_t src_len, 116 | uint8_t* dst, const size_t dst_len 117 | ) { 118 | size_t tx_remain = src_len, rx_remain = dst_len; 119 | 120 | #if defined(PSRAM_MUTEX) 121 | mutex_enter_blocking(&spi->mtx); 122 | #elif defined(PSRAM_SPINLOCK) 123 | spi->spin_irq_state = spin_lock_blocking(spi->spinlock); 124 | #endif 125 | io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm]; 126 | while (tx_remain) { 127 | if (!pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) { 128 | *txfifo = *src++; 129 | --tx_remain; 130 | } 131 | } 132 | 133 | io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm]; 134 | while (rx_remain) { 135 | if (!pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) { 136 | *dst++ = *rxfifo; 137 | --rx_remain; 138 | } 139 | } 140 | 141 | #if defined(PSRAM_MUTEX) 142 | mutex_exit(&spi->mtx); 143 | #elif defined(PSRAM_SPINLOCK) 144 | spin_unlock(spi->spinlock, spi->spin_irq_state); 145 | #endif 146 | } 147 | 148 | /** 149 | * @brief Write raw data to the PSRAM SPI PIO, driven by DMA without CPU 150 | * involvement. 151 | * 152 | * It's recommended to use DMA when possible as it's higher speed. Used to send 153 | * raw commands to the PSRAM. This function is faster than 154 | * pio_spi_write_read_dma_blocking() if no data is to be read. 155 | * 156 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 157 | * @param src Pointer to the source data to write. 158 | * @param src_len Length of the source data in bytes. 159 | */ 160 | __force_inline static void __time_critical_func(pio_spi_write_dma_blocking)( 161 | psram_spi_inst_t* spi, 162 | const uint8_t* src, const size_t src_len 163 | ) { 164 | #ifdef PSRAM_MUTEX 165 | mutex_enter_blocking(&spi->mtx); 166 | #elif defined(PSRAM_SPINLOCK) 167 | spi->spin_irq_state = spin_lock_blocking(spi->spinlock); 168 | #endif // PSRAM_SPINLOCK 169 | #if defined(PSRAM_WAITDMA) 170 | #if defined(PSRAM_ASYNC) 171 | dma_channel_wait_for_finish_blocking(spi->async_dma_chan); 172 | #endif // PSRAM_ASYNC 173 | dma_channel_wait_for_finish_blocking(spi->write_dma_chan); 174 | dma_channel_wait_for_finish_blocking(spi->read_dma_chan); 175 | #endif // PSRAM_WAITDMA 176 | dma_channel_transfer_from_buffer_now(spi->write_dma_chan, src, src_len); 177 | dma_channel_wait_for_finish_blocking(spi->write_dma_chan); 178 | #ifdef PSRAM_MUTEX 179 | mutex_exit(&spi->mtx); 180 | #elif defined(PSRAM_SPINLOCK) 181 | spin_unlock(spi->spinlock, spi->spin_irq_state); 182 | #endif // PSRAM_SPINLOCK 183 | } 184 | 185 | /** 186 | * @brief Write and read raw data to the PSRAM SPI PIO, driven by DMA without CPU 187 | * involvement. 188 | * 189 | * It's recommended to use DMA when possible as it's higher speed. Used to send 190 | * raw commands and receive data from the PSRAM. Usually the @c psram_write* and 191 | * @c psram_read* commands should be used instead. 192 | * 193 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 194 | * @param src Pointer to the source data to write. 195 | * @param src_len Length of the source data in bytes. 196 | * @param dst Pointer to the destination for read data, if any. Set to 0 or NULL 197 | * if no data is to be read. 198 | * @param dst_len Length of the destination data in bytes. Set to 0 if no data 199 | * is to be read. 200 | */ 201 | __force_inline static void __time_critical_func(pio_spi_write_read_dma_blocking)( 202 | psram_spi_inst_t* spi, 203 | const uint8_t* src, const size_t src_len, 204 | uint8_t* dst, const size_t dst_len 205 | ) { 206 | #ifdef PSRAM_MUTEX 207 | mutex_enter_blocking(&spi->mtx); 208 | #elif defined(PSRAM_SPINLOCK) 209 | spi->spin_irq_state = spin_lock_blocking(spi->spinlock); 210 | #endif // PSRAM_SPINLOCK 211 | #if defined(PSRAM_WAITDMA) 212 | #if defined(PSRAM_ASYNC) 213 | dma_channel_wait_for_finish_blocking(spi->async_dma_chan); 214 | #endif // PSRAM_ASYNC 215 | dma_channel_wait_for_finish_blocking(spi->write_dma_chan); 216 | dma_channel_wait_for_finish_blocking(spi->read_dma_chan); 217 | #endif // PSRAM_WAITDMA 218 | dma_channel_transfer_from_buffer_now(spi->write_dma_chan, src, src_len); 219 | dma_channel_transfer_to_buffer_now(spi->read_dma_chan, dst, dst_len); 220 | dma_channel_wait_for_finish_blocking(spi->write_dma_chan); 221 | dma_channel_wait_for_finish_blocking(spi->read_dma_chan); 222 | #ifdef PSRAM_MUTEX 223 | mutex_exit(&spi->mtx); 224 | #elif defined(PSRAM_SPINLOCK) 225 | spin_unlock(spi->spinlock, spi->spin_irq_state); 226 | #endif // PSRAM_SPINLOCK 227 | } 228 | 229 | /** 230 | * @brief Write raw data asynchronously to the PSRAM SPI PIO, driven by DMA without CPU 231 | * involvement. 232 | * 233 | * Used to send raw commands to the PSRAM. Usually the @c psram_write*_async() 234 | * command should be used instead. 235 | * 236 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 237 | * @param src Pointer to the source data to write. 238 | * @param src_len Length of the source data in bytes. 239 | */ 240 | #if defined(PSRAM_ASYNC) 241 | __force_inline static void __time_critical_func(pio_spi_write_async)( 242 | psram_spi_inst_t* spi, 243 | const uint8_t* src, const size_t src_len 244 | ) { 245 | #if defined(PSRAM_ASYNC_SYNCHRONIZE) 246 | #ifdef PSRAM_MUTEX 247 | mutex_enter_blocking(&spi->mtx); 248 | #elif defined(PSRAM_SPINLOCK) 249 | spi->spin_irq_state = spin_lock_blocking(spi->spinlock); 250 | #endif // PSRAM_SPINLOCK 251 | #endif // defined(PSRAM_ASYNC_SYNCHRONIZE) 252 | // Wait for all DMA to PSRAM to complete 253 | dma_channel_wait_for_finish_blocking(spi->write_dma_chan); 254 | dma_channel_wait_for_finish_blocking(spi->read_dma_chan); 255 | dma_channel_wait_for_finish_blocking(spi->async_dma_chan); 256 | async_spi_inst = spi; 257 | 258 | dma_channel_transfer_from_buffer_now(spi->async_dma_chan, src, src_len); 259 | } 260 | #endif 261 | 262 | 263 | /** 264 | * @brief Initialize the PSRAM over SPI. This function must be called before 265 | * accessing PSRAM. 266 | * 267 | * @param pio The PIO instance to use (PIO0 or PIO1). 268 | * @param sm The state machine number in the PIO module to use. If -1 is given, 269 | * will use the first available state machine. 270 | * @param clkdiv Clock divisor for the state machine. At RP2040 speeds greater 271 | * than 280MHz, a clkdiv >1.0 is needed. For example, at 400MHz, a clkdiv of 272 | * 1.6 is recommended. 273 | * @param fudge Whether to insert an extra "fudge factor" of one clock cycle 274 | * before reading from the PSRAM. Depending on your PCB layout or PSRAM type, 275 | * you may need to do this. 276 | * 277 | * @return The PSRAM configuration instance. This instance should be passed to 278 | * all PSRAM access functions. 279 | */ 280 | psram_spi_inst_t psram_spi_init_clkdiv(PIO pio, int sm, float clkdiv, bool fudge); 281 | 282 | /** 283 | * @brief Initialize the PSRAM over SPI. This function must be called before 284 | * accessing PSRAM. 285 | * 286 | * Defaults to a clkdiv of 1.0. This function is provided for backwards 287 | * compatibility. Use psram_spi_init_clkdiv instead if you want a clkdiv other 288 | * than 1.0. 289 | * 290 | * @param pio The PIO instance to use (PIO0 or PIO1). 291 | * @param sm The state machine number in the PIO module to use. If -1 is given, 292 | * will use the first available state machine. 293 | * 294 | * @return The PSRAM configuration instance. This instance should be passed to 295 | * all PSRAM access functions. 296 | */ 297 | psram_spi_inst_t psram_spi_init(PIO pio, int sm); 298 | int test_psram(psram_spi_inst_t* psram_spi, int increment); 299 | 300 | void psram_spi_uninit(psram_spi_inst_t spi, bool fudge); 301 | 302 | static uint8_t write8_command[] = { 303 | 40, // 40 bits write 304 | 0, // 0 bits read 305 | 0x02u, // Write command 306 | 0, 0, 0, // Address 307 | 0 // 8 bits data 308 | }; 309 | /** 310 | * @brief Write 8 bits of data to a given address asynchronously to the PSRAM SPI PIO, 311 | * driven by DMA without CPU involvement. 312 | * 313 | * This function is optimized to write 8 bits as quickly as possible to the 314 | * PSRAM as opposed to the more general-purpose psram_write() function. 315 | * 316 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 317 | * @param addr Address to write to. 318 | * @param val Value to write. 319 | */ 320 | #if defined(PSRAM_ASYNC) 321 | __force_inline static void psram_write8_async(psram_spi_inst_t* spi, uint32_t addr, uint8_t val) { 322 | write8_command[3] = addr >> 16; 323 | write8_command[4] = addr >> 8; 324 | write8_command[5] = addr; 325 | write8_command[6] = val; 326 | 327 | pio_spi_write_async(spi, write8_command, sizeof(write8_command)); 328 | }; 329 | #endif 330 | 331 | 332 | /** 333 | * @brief Write 8 bits of data to a given address to the PSRAM SPI PIO, 334 | * driven by DMA without CPU involvement, blocking until the write is 335 | * complete. 336 | * 337 | * This function is optimized to write 8 bits as quickly as possible to the 338 | * PSRAM as opposed to the more general-purpose psram_write() function. 339 | * 340 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 341 | * @param addr Address to write to. 342 | * @param val Value to write. 343 | */ 344 | __force_inline static void psram_write8(psram_spi_inst_t* spi, uint32_t addr, uint8_t val) { 345 | write8_command[3] = addr >> 16; 346 | write8_command[4] = addr >> 8; 347 | write8_command[5] = addr; 348 | write8_command[6] = val; 349 | 350 | pio_spi_write_dma_blocking(spi, write8_command, sizeof(write8_command)); 351 | }; 352 | 353 | 354 | static uint8_t read8_command[] = { 355 | 40, // 40 bits write 356 | 8, // 8 bits read 357 | 0x0bu, // Fast read command 358 | 0, 0, 0, // Address 359 | 0 // 8 delay cycles 360 | }; 361 | /** 362 | * @brief Read 8 bits of data from a given address to the PSRAM SPI PIO, 363 | * driven by DMA without CPU involvement, blocking until the read is 364 | * complete. 365 | * 366 | * This function is optimized to read 8 bits as quickly as possible from the 367 | * PSRAM as opposed to the more general-purpose psram_read() function. 368 | * 369 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 370 | * @param addr Address to read from. 371 | * @return The data at the specified address. 372 | */ 373 | __force_inline static uint8_t psram_read8(psram_spi_inst_t* spi, uint32_t addr) { 374 | read8_command[3] = addr >> 16; 375 | read8_command[4] = addr >> 8; 376 | read8_command[5] = addr; 377 | 378 | uint8_t val; 379 | pio_spi_write_read_dma_blocking(spi, read8_command, sizeof(read8_command), &val, 1); 380 | return val; 381 | }; 382 | 383 | 384 | static uint8_t write16_command[] = { 385 | 48, // 48 bits write 386 | 0, // 0 bits read 387 | 0x02u, // Write command 388 | 0, 0, 0, // Address 389 | 0, 0 // 16 bits data 390 | }; 391 | /** 392 | * @brief Write 16 bits of data to a given address to the PSRAM SPI PIO, 393 | * driven by DMA without CPU involvement, blocking until the write is 394 | * complete. 395 | * 396 | * This function is optimized to write 16 bits as quickly as possible to the 397 | * PSRAM as opposed to the more general-purpose psram_write() function. 398 | * 399 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 400 | * @param addr Address to write to. 401 | * @param val Value to write. 402 | */ 403 | __force_inline static void psram_write16(psram_spi_inst_t* spi, uint32_t addr, uint16_t val) { 404 | write16_command[3] = addr >> 16; 405 | write16_command[4] = addr >> 8; 406 | write16_command[5] = addr; 407 | write16_command[6] = val; 408 | write16_command[7] = val >> 8; 409 | 410 | pio_spi_write_dma_blocking(spi, write16_command, sizeof(write16_command)); 411 | }; 412 | 413 | 414 | static uint8_t read16_command[] = { 415 | 40, // 40 bits write 416 | 16, // 16 bits read 417 | 0x0bu, // Fast read command 418 | 0, 0, 0, // Address 419 | 0 // 8 delay cycles 420 | }; 421 | /** 422 | * @brief Read 16 bits of data from a given address to the PSRAM SPI PIO, 423 | * driven by DMA without CPU involvement, blocking until the read is 424 | * complete. 425 | * 426 | * This function is optimized to read 16 bits as quickly as possible from the 427 | * PSRAM as opposed to the more general-purpose psram_read() function. 428 | * 429 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 430 | * @param addr Address to read from. 431 | * @return The data at the specified address. 432 | */ 433 | __force_inline static uint16_t psram_read16(psram_spi_inst_t* spi, uint32_t addr) { 434 | read16_command[3] = addr >> 16; 435 | read16_command[4] = addr >> 8; 436 | read16_command[5] = addr; 437 | 438 | uint16_t val; 439 | pio_spi_write_read_dma_blocking(spi, read16_command, sizeof(read16_command), (unsigned char*)&val, 2); 440 | return val; 441 | }; 442 | 443 | 444 | static uint8_t write32_command[] = { 445 | 64, // 64 bits write 446 | 0, // 0 bits read 447 | 0x02u, // Write command 448 | 0, 0, 0, // Address 449 | 0, 0, 0, 0 // 32 bits data 450 | }; 451 | /** 452 | * @brief Write 32 bits of data to a given address to the PSRAM SPI PIO, 453 | * driven by DMA without CPU involvement, blocking until the write is 454 | * complete. 455 | * 456 | * This function is optimized to write 32 bits as quickly as possible to the 457 | * PSRAM as opposed to the more general-purpose psram_write() function. 458 | * 459 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 460 | * @param addr Address to write to. 461 | * @param val Value to write. 462 | */ 463 | __force_inline static void psram_write32(psram_spi_inst_t* spi, uint32_t addr, uint32_t val) { 464 | // Break the address into three bytes and send read command 465 | write32_command[3] = addr >> 16; 466 | write32_command[4] = addr >> 8; 467 | write32_command[5] = addr; 468 | write32_command[6] = val; 469 | write32_command[7] = val >> 8; 470 | write32_command[8] = val >> 16; 471 | write32_command[9] = val >> 24; 472 | 473 | pio_spi_write_dma_blocking(spi, write32_command, sizeof(write32_command)); 474 | }; 475 | 476 | 477 | /** 478 | * @brief Write 32 bits of data to a given address asynchronously to the PSRAM 479 | * SPI PIO, driven by DMA without CPU involvement. 480 | * 481 | * This function is optimized to write 32 bits as quickly as possible to the 482 | * PSRAM as opposed to the more general-purpose psram_write() function. 483 | * 484 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 485 | * @param addr Address to write to. 486 | * @param val Value to write. 487 | */ 488 | __force_inline static void psram_write32_async(psram_spi_inst_t* spi, uint32_t addr, uint32_t val) { 489 | // Break the address into three bytes and send read command 490 | write32_command[3] = addr >> 16; 491 | write32_command[4] = addr >> 8; 492 | write32_command[5] = addr; 493 | write32_command[6] = val; 494 | write32_command[7] = val >> 8; 495 | write32_command[8] = val >> 16; 496 | write32_command[9] = val >> 24; 497 | 498 | pio_spi_write_async(spi, write32_command, sizeof(write32_command)); 499 | }; 500 | 501 | 502 | static uint8_t read32_command[] = { 503 | 40, // 40 bits write 504 | 32, // 32 bits read 505 | 0x0bu, // Fast read command 506 | 0, 0, 0, // Address 507 | 0 // 8 delay cycles 508 | }; 509 | /** 510 | * @brief Read 32 bits of data from a given address to the PSRAM SPI PIO, 511 | * driven by DMA without CPU involvement, blocking until the read is 512 | * complete. 513 | * 514 | * This function is optimized to read 32 bits as quickly as possible from the 515 | * PSRAM as opposed to the more general-purpose psram_read() function. 516 | * 517 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 518 | * @param addr Address to read from. 519 | * @return The data at the specified address. 520 | */ 521 | __force_inline static uint32_t psram_read32(psram_spi_inst_t* spi, uint32_t addr) { 522 | read32_command[3] = addr >> 16; 523 | read32_command[4] = addr >> 8; 524 | read32_command[5] = addr; 525 | 526 | uint32_t val; 527 | pio_spi_write_read_dma_blocking(spi, read32_command, sizeof(read32_command), (unsigned char*)&val, 4); 528 | return val; 529 | }; 530 | 531 | 532 | static uint8_t write_command[] = { 533 | 0, // n bits write 534 | 0, // 0 bits read 535 | 0x02u, // Fast write command 536 | 0, 0, 0 // Address 537 | }; 538 | /** 539 | * @brief Write @c count bytes of data to a given address to the PSRAM SPI PIO, 540 | * driven by DMA without CPU involvement, blocking until the write is 541 | * complete. 542 | * 543 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 544 | * @param addr Address to write to. 545 | * @param src Pointer to the source data to write. 546 | * @param count Number of bytes to write. 547 | */ 548 | __force_inline static void psram_write(psram_spi_inst_t* spi, const uint32_t addr, const uint8_t* src, const size_t count) { 549 | // Break the address into three bytes and send read command 550 | write_command[0] = (4 + count) * 8; 551 | write_command[3] = addr >> 16; 552 | write_command[4] = addr >> 8; 553 | write_command[5] = addr; 554 | 555 | pio_spi_write_dma_blocking(spi, write_command, sizeof(write_command)); 556 | pio_spi_write_dma_blocking(spi, src, count); 557 | }; 558 | 559 | 560 | static uint8_t read_command[] = { 561 | 40, // 40 bits write 562 | 0, // n bits read 563 | 0x0bu, // Fast read command 564 | 0, 0, 0, // Address 565 | 0 // 8 delay cycles 566 | }; 567 | /** 568 | * @brief Read @c count bits of data from a given address to the PSRAM SPI PIO, 569 | * driven by DMA without CPU involvement, blocking until the read is 570 | * complete. 571 | * 572 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 573 | * @param addr Address to read from. 574 | * @param dst Pointer to the destination for the read data. 575 | * @param count Number of bytes to read. 576 | */ 577 | __force_inline static void psram_read(psram_spi_inst_t* spi, const uint32_t addr, uint8_t* dst, const size_t count) { 578 | read_command[1] = count * 8; 579 | read_command[3] = addr >> 16; 580 | read_command[4] = addr >> 8; 581 | read_command[5] = addr; 582 | 583 | pio_spi_write_read_dma_blocking(spi, read_command, sizeof(read_command), dst, count); 584 | }; 585 | 586 | 587 | static uint8_t write_async_fast_command[134] = { 588 | 0, // n bits write 589 | 0, // 0 bits read 590 | 0x02u // Fast write command 591 | }; 592 | /** 593 | * @brief Write @c count bytes of data to a given address asynchronously to the 594 | * PSRAM SPI PIO, driven by DMA without CPU involvement. 595 | * 596 | * @param spi The PSRAM configuration instance returned from psram_spi_init(). 597 | * @param addr Address to write to. 598 | * @param src Pointer to the source data to write. 599 | * @param count Number of bytes to write. 600 | */ 601 | __force_inline static void psram_write_async_fast(psram_spi_inst_t* spi, uint32_t addr, uint8_t* val, const size_t count) { 602 | write_async_fast_command[0] = (4 + count) * 8; 603 | write_async_fast_command[3] = addr >> 16; 604 | write_async_fast_command[4] = addr >> 8; 605 | write_async_fast_command[5] = addr; 606 | 607 | memcpy(write_async_fast_command + 6, val, count); 608 | 609 | pio_spi_write_async(spi, write_async_fast_command, 6 + count); 610 | }; 611 | 612 | 613 | #ifdef __cplusplus 614 | } 615 | #endif 616 | -------------------------------------------------------------------------------- /psram_spi.pio: -------------------------------------------------------------------------------- 1 | ; rp2040-psram 2 | ; 3 | ; Copyright © 2023 Ian Scott 4 | ; 5 | ; Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | ; this software and associated documentation files (the “Software”), to deal in 7 | ; the Software without restriction, including without limitation the rights to 8 | ; use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | ; of the Software, and to permit persons to whom the Software is furnished to do 10 | ; so, subject to the following conditions: 11 | ; 12 | ; The above copyright notice and this permission notice shall be included in all 13 | ; copies or substantial portions of the Software. 14 | ; 15 | ; THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | ; SOFTWARE. 22 | 23 | ; SPI, customized as the PSRAM chips like to implement it: 24 | ; - Data is always written first, then optionally read 25 | ; Depending on PCB layout, introduce fudge factor: 26 | ; - Reads in high speed mode need an extra clock cycle to synchronize 27 | ; - Reads are done on the falling edge of SCK when > 83MHz 28 | 29 | .program spi_psram_fudge 30 | .side_set 2 ; sideset bit 1 is SCK, bit 0 is CS 31 | begin: 32 | out x, 8 side 0b01 ; x = number of bits to output. CS deasserted 33 | out y, 8 side 0b01 ; y = number of bits to input 34 | jmp x--, writeloop side 0b01 ; Pre-decement x by 1 so loop has correct number of iterations 35 | writeloop: 36 | out pins, 1 side 0b00 ; Write value on pin, lower clock. CS asserted 37 | jmp x--, writeloop side 0b10 ; Raise clock: this is when PSRAM reads the value. Loop if we have more to write 38 | jmp !y, begin side 0b00 ; If this is a write-only operation, jump back to beginning 39 | nop side 0b10 ; Fudge factor of extra clock cycle; the PSRAM needs 1 extra for output to start appearing 40 | jmp readloop_mid side 0b00 ; Jump to middle of readloop to decrement y and get right clock phase 41 | readloop: 42 | in pins, 1 side 0b00 ; Read value on pin, lower clock. Datasheet says to read on falling edge > 83MHz 43 | readloop_mid: 44 | jmp y--, readloop side 0b10 ; Raise clock. Loop if we have more to read 45 | 46 | .program spi_psram 47 | .side_set 2 ; sideset bit 1 is SCK, bit 0 is CS 48 | begin: 49 | out x, 8 side 0b01 ; x = number of bits to output. CS deasserted 50 | out y, 8 side 0b01 ; y = number of bits to input 51 | jmp x--, writeloop side 0b01 ; Pre-decement x by 1 so loop has correct number of iterations 52 | writeloop: 53 | out pins, 1 side 0b00 ; Write value on pin, lower clock. CS asserted 54 | jmp x--, writeloop side 0b10 ; Raise clock: this is when PSRAM reads the value. Loop if we have more to write 55 | jmp !y, begin side 0b00 ; If this is a write-only operation, jump back to beginning 56 | jmp readloop_mid side 0b10 ; Jump to middle of readloop to decrement y and get right clock phase 57 | readloop: 58 | in pins, 1 side 0b10 ; Read value on pin, raise clock. 59 | readloop_mid: 60 | jmp y--, readloop side 0b00 ; Lower clock. Loop if we have more to read 61 | 62 | % c-sdk { 63 | #include "hardware/gpio.h" 64 | static inline void pio_spi_psram_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool fudge, uint pin_cs, uint pin_mosi, uint pin_miso) { 65 | pio_sm_config c; 66 | if (fudge) { 67 | c = spi_psram_fudge_program_get_default_config(prog_offs); 68 | } else { 69 | c = spi_psram_program_get_default_config(prog_offs); 70 | } 71 | sm_config_set_out_pins(&c, pin_mosi, 1); 72 | sm_config_set_in_pins(&c, pin_miso); 73 | sm_config_set_sideset_pins(&c, pin_cs); 74 | sm_config_set_out_shift(&c, false, true, n_bits); 75 | sm_config_set_in_shift(&c, false, true, n_bits); 76 | sm_config_set_clkdiv(&c, clkdiv); 77 | 78 | pio_sm_set_consecutive_pindirs(pio, sm, pin_cs, 2, true); 79 | pio_sm_set_consecutive_pindirs(pio, sm, pin_mosi, 1, true); 80 | pio_sm_set_consecutive_pindirs(pio, sm, pin_miso, 1, false); 81 | pio_gpio_init(pio, pin_miso); 82 | pio_gpio_init(pio, pin_mosi); 83 | pio_gpio_init(pio, pin_cs); 84 | pio_gpio_init(pio, pin_cs + 1); 85 | 86 | hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso); 87 | 88 | pio_sm_init(pio, sm, prog_offs, &c); 89 | pio_sm_set_enabled(pio, sm, true); 90 | } 91 | %} 92 | 93 | .program qspi_psram 94 | .side_set 2 95 | begin: 96 | out x, 8 side 0b01 ; x = number of nibbles to output. CS deasserted 97 | out y, 8 side 0b01 ; y = number of nibbles to input 98 | jmp x--, writeloop side 0b01 ; Pre-decement x by 1 so loop has correct number of iterations 99 | writeloop: 100 | out pins, 4 side 0b00 ; Write value on pins, lower clock. CS asserted 101 | jmp x--, writeloop side 0b10 ; Raise clock: this is when PSRAM reads the value. Loop if we have more to write 102 | jmp !y, begin side 0b00 ; If this is a write-only operation, jump back to beginning 103 | set pindirs 0 side 0b10 ; Fudge factor of extra clock cycle; the PSRAM needs 1 extra for output to start appearing 104 | nop side 0b00 ; Jump to middle of readloop to decrement y and get right clock phase 105 | readloop: 106 | in pins, 4 side 0b10 ; Read value on s, lower clock. Datasheet says to read on falling edge > 83MHz 107 | readloop_mid: 108 | jmp y--, readloop side 0b00 ; Raise clock. Loop if we have more to read 109 | set pindirs 0xF side 0b01 110 | 111 | % c-sdk { 112 | #include "hardware/gpio.h" 113 | static inline void pio_qspi_psram_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, uint pin_cs, uint pin_sio0) { 114 | pio_sm_config c = qspi_psram_program_get_default_config(prog_offs); 115 | sm_config_set_out_pins(&c, pin_sio0, 4); 116 | sm_config_set_in_pins(&c, pin_sio0); 117 | sm_config_set_set_pins(&c, pin_sio0, 4); 118 | sm_config_set_sideset_pins(&c, pin_cs); 119 | sm_config_set_out_shift(&c, false, true, n_bits); 120 | sm_config_set_in_shift(&c, false, true, n_bits); 121 | sm_config_set_clkdiv(&c, clkdiv); 122 | 123 | pio_sm_set_consecutive_pindirs(pio, sm, pin_cs, 2, true); 124 | pio_sm_set_consecutive_pindirs(pio, sm, pin_sio0, 4, true); 125 | pio_gpio_init(pio, pin_sio0); 126 | pio_gpio_init(pio, pin_sio0 + 1); 127 | pio_gpio_init(pio, pin_sio0 + 2); 128 | pio_gpio_init(pio, pin_sio0 + 3); 129 | pio_gpio_init(pio, pin_cs); 130 | pio_gpio_init(pio, pin_cs + 1); 131 | 132 | hw_set_bits(&pio->input_sync_bypass, 0xfu << pin_sio0); 133 | 134 | pio_sm_init(pio, sm, prog_offs, &c); 135 | pio_sm_set_enabled(pio, sm, true); 136 | } 137 | %} 138 | --------------------------------------------------------------------------------