├── README.md ├── doc ├── IPUS_64Mbit_SQPI_PSRAM.pdf ├── LY68L6400SLIT.pdf ├── esp-psram64_esp-psram64h_datasheet_en.pdf ├── psram_mod.jpg └── speed.png ├── micropython.mk ├── mod_psram.c ├── psram.c └── psram.h /README.md: -------------------------------------------------------------------------------- 1 | # micropython-psram 2 | *8 megabyte ram for [openmv](http://openmv.io/)* 3 | 4 | Project goal: Increasing STM32F7xx and STM32H7xx RAM by 8 megabyte, while increasing cost by less than $1. 5 | 6 | [![psram mod](doc/psram_mod.jpg "psram mod")](doc/psram_mod.jpg) 7 | 8 | Two DEVEBOX STM32H7XX boards. On the board to the right the Winbond W25Q64 SPI flash memory has been replaced with a Espressif ESP-PSRAM64H, an 8 Mbyte SPI pseudo-static RAM. 9 | 10 | The board on the left already has a header pin soldered to the reset button. With RST, SWCLK and SWDIO broken out, connecting a debugger is easy. 11 | 12 | ## Module 13 | The spi ram can be accessed from micropython and from c. 14 | 15 | From micropython: 16 | 17 | ``` 18 | import psram 19 | psram.read(address, data) 20 | psram.write(address, data) 21 | ``` 22 | 23 | where address is an integer, and data is `bytes()` or `bytearray()` 24 | 25 | From c: 26 | 27 | ``` 28 | void psram_init(); 29 | void psram_read(uint32_t addr, size_t len, uint8_t *dest); 30 | void psram_write(uint32_t addr, size_t len, const uint8_t *src); 31 | ``` 32 | Micropython use example: 33 | 34 | ``` 35 | >>> import psram 36 | psram eid: a2525d0d 31913164 37 | >>> addr=0 38 | >>> src=b'abcdefgh' 39 | >>> psram.write(addr, src) 40 | >>> dest=bytes(8) 41 | >>> psram.read(addr, dest) 42 | >>> dest 43 | b'abcdefgh' 44 | ``` 45 | 46 | 47 | ## Constraints 48 | 49 | There are constraints from the processor, and constraints from the ram. 50 | 51 | - STM32F7xx/H7xx processor: The STM32 processor can memory-map spi ram for read access, but not for write access. This way you can run a program stored in external spi memory, but because there is no write access you cannot store the heap in external spi ram (like on ESP32) 52 | 53 | - ESP-PSRAM64H spi ram: If a read or write stays within the same 1kbyte page maximum SPI clock speed is 144 MHz. If a read or write crosses the 1kbyte page boundary, maximum SPI clock speed is 84 MHz. 54 | 55 | - If processor clock frequency is 216MHz, easily available SPI clock frequencies are 108MHz and 72MHz. 56 | 57 | ## Speed Measurements 58 | 59 | This is a graph of read/write speed versus bytes read (or written). 60 | 61 | ![](doc/speed.png) 62 | 63 | Vertical axis is read/write speed in mbyte/s; horizontal axis is block size in bytes. SPI bus at 36MHz. 64 | 65 | Read and write speed are identical. 66 | 67 | The page size of the memory is 1Kbyte. Writing blocks larger than 1Kbyte does not increase speed much. Writing blocks smaller than 1Kbyte does cause speed to drop off. 68 | 69 | For small writes of 4 bytes, half the time is spent writing the address, the other half writing the data. This is not efficient. 70 | 71 | ## Compiling 72 | 73 | Add the *psram* module to the micropython sources. 74 | 75 | In `mpconfigboard.mk` add: 76 | ``` 77 | MODULE_PSRAM_ENABLED=1 78 | ``` 79 | 80 | In `mpconfigboard.h`: set 81 | ``` 82 | #define MICROPY_HW_HAS_FLASH (0) 83 | ``` 84 | 85 | Remove 86 | ``` 87 | // Winbond W25Q64 64Mbit external QSPI flash 88 | #define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (26) 89 | #define MICROPY_HW_QSPIFLASH_CS (pyb_pin_QSPI_BK1_NCS) 90 | #define MICROPY_HW_QSPIFLASH_SCK (pyb_pin_QSPI_CLK) 91 | #define MICROPY_HW_QSPIFLASH_IO0 (pyb_pin_QSPI_BK1_IO0) 92 | #define MICROPY_HW_QSPIFLASH_IO1 (pyb_pin_QSPI_BK1_IO1) 93 | #define MICROPY_HW_QSPIFLASH_IO2 (pyb_pin_QSPI_BK1_IO2) 94 | #define MICROPY_HW_QSPIFLASH_IO3 (pyb_pin_QSPI_BK1_IO3) 95 | ``` 96 | 97 | Add 98 | ``` 99 | // Espressif ESP-PSRAM64H or Lyontek LY68L6400SLIT 64Mbit external QSPI sram 100 | #define MICROPY_MODULE_BUILTIN_INIT (1) 101 | #define MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2 (26) 102 | #define MICROPY_HW_QSPIRAM_CS (pyb_pin_QSPI_BK1_NCS) 103 | #define MICROPY_HW_QSPIRAM_SCK (pyb_pin_QSPI_CLK) 104 | #define MICROPY_HW_QSPIRAM_IO0 (pyb_pin_QSPI_BK1_IO0) 105 | #define MICROPY_HW_QSPIRAM_IO1 (pyb_pin_QSPI_BK1_IO1) 106 | #define MICROPY_HW_QSPIRAM_IO2 (pyb_pin_QSPI_BK1_IO2) 107 | #define MICROPY_HW_QSPIRAM_IO3 (pyb_pin_QSPI_BK1_IO3) 108 | ``` 109 | 110 | 111 | ## Cost 112 | 113 | A 8 mbyte spi sram costs [1.3 USD](https://www.google.com/search?q=esp-psram64h+site%3A.aliexpress.com), quantity 1, shipping included. For comparison, a 8 mbyte spi flash costs 0.5 USD, quantity 1. (2020 prices) 114 | 115 | ## Datasheets 116 | The following "Made in Asia" components seem to be very similar: 117 | 118 | - [Espressif ESP-PSRAM64H](doc/esp-psram64_esp-psram64h_datasheet_en.pdf) 119 | - [Lyontek LY68L6400SLIT](doc/LY68L6400SLIT.pdf) and at [lcsc](https://lcsc.com/product-detail/RAM_Lyontek-Inc-LY68L6400SLIT_C261881.html) 120 | - [IPS6404LSQ](doc/IPUS_64Mbit_SQPI_PSRAM.pdf) -------------------------------------------------------------------------------- /doc/IPUS_64Mbit_SQPI_PSRAM.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koendv/micropython-psram/b54832e1fb3e807745d89df0286b7320d51c0be0/doc/IPUS_64Mbit_SQPI_PSRAM.pdf -------------------------------------------------------------------------------- /doc/LY68L6400SLIT.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koendv/micropython-psram/b54832e1fb3e807745d89df0286b7320d51c0be0/doc/LY68L6400SLIT.pdf -------------------------------------------------------------------------------- /doc/esp-psram64_esp-psram64h_datasheet_en.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koendv/micropython-psram/b54832e1fb3e807745d89df0286b7320d51c0be0/doc/esp-psram64_esp-psram64h_datasheet_en.pdf -------------------------------------------------------------------------------- /doc/psram_mod.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koendv/micropython-psram/b54832e1fb3e807745d89df0286b7320d51c0be0/doc/psram_mod.jpg -------------------------------------------------------------------------------- /doc/speed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/koendv/micropython-psram/b54832e1fb3e807745d89df0286b7320d51c0be0/doc/speed.png -------------------------------------------------------------------------------- /micropython.mk: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # psram 3 | 4 | ifeq ($(MODULE_PSRAM_ENABLED),1) 5 | PSRAM_DIR := $(USERMOD_DIR) 6 | INC += \ 7 | -I$(PSRAM_DIR)/include 8 | 9 | SRC_MOD += $(addprefix $(PSRAM_DIR)/,\ 10 | mod_psram.c \ 11 | psram.c \ 12 | ) 13 | 14 | endif 15 | -------------------------------------------------------------------------------- /mod_psram.c: -------------------------------------------------------------------------------- 1 | #include "py/obj.h" 2 | #include "py/objarray.h" 3 | #include "py/objstr.h" 4 | #include "py/runtime.h" 5 | #include "py/mpconfig.h" 6 | #include "psram.h" 7 | 8 | #if defined(MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2) 9 | 10 | // ----------------------------------------------------------------------------- 11 | // module definitions 12 | 13 | #define mp_raise_RuntimeError(msg) (mp_raise_msg(&mp_type_RuntimeError, (msg))) 14 | 15 | STATIC mp_obj_t mp_psram_init(); 16 | STATIC mp_obj_t mp_psram_read(mp_obj_t addr_obj, mp_obj_t data_obj); 17 | STATIC mp_obj_t mp_psram_write(mp_obj_t addr_obj, mp_obj_t data_obj); 18 | 19 | STATIC MP_DEFINE_CONST_FUN_OBJ_0(mp_psram_init_obj, mp_psram_init); 20 | STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_psram_read_obj, mp_psram_read); 21 | STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_psram_write_obj, mp_psram_write); 22 | 23 | STATIC const mp_rom_map_elem_t psram_module_globals_table[] = { 24 | {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_psram)}, 25 | {MP_ROM_QSTR(MP_QSTR___init__), MP_ROM_PTR(&mp_psram_init_obj)}, 26 | {MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_psram_read_obj)}, 27 | {MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_psram_write_obj)}, 28 | }; 29 | 30 | MP_DEFINE_CONST_DICT(psram_globals_dict, psram_module_globals_table); 31 | 32 | typedef struct _mp_obj_psram_t { 33 | mp_obj_base_t base; 34 | } mp_obj_psram_t; 35 | 36 | const mp_obj_module_t psram_module = { 37 | .base = {&mp_type_module}, 38 | .globals = (mp_obj_dict_t *)&psram_globals_dict, 39 | }; 40 | 41 | // Register the module to make it available in Python 42 | MP_REGISTER_MODULE(MP_QSTR_psram, psram_module, (1)); 43 | 44 | // ----------------------------------------------------------------------------- 45 | // code 46 | 47 | /* access to bytes/bytearray contents */ 48 | 49 | /* 50 | o: micropython string, bytes or bytearray (input) 51 | len: length of string, bytes or bytearray (output) 52 | items: output, string, bytes or bytearray data (output) 53 | */ 54 | 55 | static void mp_obj_get_data(mp_obj_t o, size_t *len, mp_obj_t **items) { 56 | if (mp_obj_is_type(MP_OBJ_FROM_PTR(o), &mp_type_bytearray)) { 57 | mp_obj_array_t *barray = MP_OBJ_FROM_PTR(o); 58 | *len = barray->len; 59 | *items = barray->items; 60 | return; 61 | } 62 | if (mp_obj_is_type(MP_OBJ_FROM_PTR(o), &mp_type_bytes) || 63 | mp_obj_is_type(MP_OBJ_FROM_PTR(o), &mp_type_str)) { 64 | mp_obj_str_t *str = MP_OBJ_FROM_PTR(o); 65 | *len = str->len; 66 | *items = (void *)str->data; 67 | return; 68 | } 69 | mp_raise_TypeError(MP_ERROR_TEXT("object not string, bytes nor bytearray")); 70 | } 71 | 72 | STATIC mp_obj_t mp_psram_init() { 73 | psram_init(); 74 | return mp_const_none; 75 | } 76 | 77 | STATIC mp_obj_t mp_psram_read(mp_obj_t addr_obj, mp_obj_t dest_obj) { 78 | uint32_t addr = mp_obj_get_int(addr_obj); 79 | size_t len; 80 | mp_obj_t *dest; 81 | mp_obj_get_data(dest_obj, &len, &dest); 82 | if (len != 0) { 83 | psram_read(addr, len, (uint8_t *)dest); 84 | } 85 | return mp_const_none; 86 | }; 87 | 88 | STATIC mp_obj_t mp_psram_write(mp_obj_t addr_obj, mp_obj_t src_obj) { 89 | uint32_t addr = mp_obj_get_int(addr_obj); 90 | size_t len; 91 | mp_obj_t *src; 92 | mp_obj_get_data(src_obj, &len, &src); 93 | if (len != 0) { 94 | psram_write(addr, len, (uint8_t *)src); 95 | } 96 | return mp_const_none; 97 | }; 98 | 99 | #endif // defined(MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2) 100 | // not truncated 101 | -------------------------------------------------------------------------------- /psram.c: -------------------------------------------------------------------------------- 1 | #include "py/obj.h" 2 | #include "py/runtime.h" 3 | #include "py/mpconfig.h" 4 | #include "mpu.h" 5 | #include "qspi.h" 6 | #include "pin_static_af.h" 7 | #include "psram.h" 8 | 9 | #if defined(MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2) 10 | 11 | // from ESP-PSRAM64H/LY68L6400SLIT datasheet 12 | #define SRAM_CMD_READ 0x03 13 | #define SRAM_CMD_FAST_READ 0x0b 14 | #define SRAM_CMD_QUAD_READ 0xeb 15 | #define SRAM_CMD_WRITE 0x02 16 | #define SRAM_CMD_QUAD_WRITE 0x38 17 | #define SRAM_CMD_QUAD_ON 0x35 18 | #define SRAM_CMD_QUAD_OFF 0xf5 19 | #define SRAM_CMD_RST_EN 0x66 20 | #define SRAM_CMD_RST 0x99 21 | #define SRAM_CMD_BURST_LEN 0xc0 22 | #define SRAM_CMD_READ_ID 0x9f 23 | 24 | #define QSPI_MAP_ADDR (0x90000000) 25 | 26 | #ifndef MICROPY_HW_QSPI_PRESCALER 27 | #define MICROPY_HW_QSPI_PRESCALER 3 // F_CLK = F_AHB/3 (72MHz when CPU is 216MHz) 28 | #endif 29 | 30 | #ifndef MICROPY_HW_QSPI_SAMPLE_SHIFT 31 | #define MICROPY_HW_QSPI_SAMPLE_SHIFT 1 // sample shift enabled 32 | #endif 33 | 34 | #ifndef MICROPY_HW_QSPI_TIMEOUT_COUNTER 35 | #define MICROPY_HW_QSPI_TIMEOUT_COUNTER 0 // timeout counter disabled (see F7 errata) 36 | #endif 37 | 38 | #ifndef MICROPY_HW_QSPI_CS_HIGH_CYCLES 39 | #define MICROPY_HW_QSPI_CS_HIGH_CYCLES 2 // nCS stays high for 2 cycles 40 | #endif 41 | 42 | #if (MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2 - 3 - 1) >= 24 43 | #define QSPI_CMD 0xec 44 | #define QSPI_ADSIZE 3 45 | #else 46 | #define QSPI_CMD 0xeb 47 | #define QSPI_ADSIZE 2 48 | #endif 49 | 50 | #define mp_raise_RuntimeError(msg) (mp_raise_msg(&mp_type_RuntimeError, (msg))) 51 | 52 | static inline void qspi_mpu_disable_all(void) { 53 | // Configure MPU to disable access to entire QSPI region, to prevent CPU 54 | // speculative execution from accessing this region and modifying QSPI registers. 55 | uint32_t irq_state = mpu_config_start(); 56 | mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_DISABLE(0x00, MPU_REGION_SIZE_256MB)); 57 | mpu_config_end(irq_state); 58 | } 59 | 60 | static inline void qspi_mpu_enable_mapped(void) { 61 | // Configure MPU to allow access to only the valid part of external SPI flash. 62 | // The memory accesses to the mapped QSPI are faster if the MPU is not used 63 | // for the memory-mapped region, so 3 MPU regions are used to disable access 64 | // to everything except the valid address space, using holes in the bottom 65 | // of the regions and nesting them. 66 | // At the moment this is hard-coded to 2MiB of QSPI address space. 67 | uint32_t irq_state = mpu_config_start(); 68 | mpu_config_region(MPU_REGION_QSPI1, QSPI_MAP_ADDR, MPU_CONFIG_DISABLE(0x01, MPU_REGION_SIZE_256MB)); 69 | mpu_config_region(MPU_REGION_QSPI2, QSPI_MAP_ADDR, MPU_CONFIG_DISABLE(0x0f, MPU_REGION_SIZE_32MB)); 70 | mpu_config_region(MPU_REGION_QSPI3, QSPI_MAP_ADDR, MPU_CONFIG_DISABLE(0x01, MPU_REGION_SIZE_16MB)); 71 | mpu_config_end(irq_state); 72 | } 73 | 74 | void qspi_init(void) { 75 | qspi_mpu_disable_all(); 76 | 77 | // Configure pins 78 | mp_hal_pin_config_alt_static_speed(MICROPY_HW_QSPIRAM_CS, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_VERY_HIGH, STATIC_AF_QUADSPI_BK1_NCS); 79 | mp_hal_pin_config_alt_static_speed(MICROPY_HW_QSPIRAM_SCK, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_VERY_HIGH, STATIC_AF_QUADSPI_CLK); 80 | mp_hal_pin_config_alt_static_speed(MICROPY_HW_QSPIRAM_IO0, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_VERY_HIGH, STATIC_AF_QUADSPI_BK1_IO0); 81 | mp_hal_pin_config_alt_static_speed(MICROPY_HW_QSPIRAM_IO1, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_VERY_HIGH, STATIC_AF_QUADSPI_BK1_IO1); 82 | mp_hal_pin_config_alt_static_speed(MICROPY_HW_QSPIRAM_IO2, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_VERY_HIGH, STATIC_AF_QUADSPI_BK1_IO2); 83 | mp_hal_pin_config_alt_static_speed(MICROPY_HW_QSPIRAM_IO3, MP_HAL_PIN_MODE_ALT, MP_HAL_PIN_PULL_NONE, MP_HAL_PIN_SPEED_VERY_HIGH, STATIC_AF_QUADSPI_BK1_IO3); 84 | 85 | // Bring up the QSPI peripheral 86 | 87 | __HAL_RCC_QSPI_CLK_ENABLE(); 88 | __HAL_RCC_QSPI_FORCE_RESET(); 89 | __HAL_RCC_QSPI_RELEASE_RESET(); 90 | 91 | /* 92 | spi sram does internal refresh when NCS is high. 93 | keeping NCS low causes sram memory failure. 94 | to raise NCS after a read or write: 95 | - in QUADSPI->CR, set QUADSPI_CR_TCEN 96 | - set QUADSPI->LPTR to low value 97 | */ 98 | 99 | QUADSPI->CR = 100 | (MICROPY_HW_QSPI_PRESCALER - 1) << QUADSPI_CR_PRESCALER_Pos 101 | | 3 << QUADSPI_CR_FTHRES_Pos // 4 bytes must be available to read/write 102 | #if defined(QUADSPI_CR_FSEL_Pos) 103 | | 0 << QUADSPI_CR_FSEL_Pos // FLASH 1 selected 104 | #endif 105 | #if defined(QUADSPI_CR_DFM_Pos) 106 | | 0 << QUADSPI_CR_DFM_Pos // dual-flash mode disabled 107 | #endif 108 | #if defined(MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2) 109 | | 1 << QUADSPI_CR_TCEN_Pos // raise NCS when mmapped sram not in use 110 | #endif 111 | | MICROPY_HW_QSPI_SAMPLE_SHIFT << QUADSPI_CR_SSHIFT_Pos 112 | | MICROPY_HW_QSPI_TIMEOUT_COUNTER << QUADSPI_CR_TCEN_Pos 113 | | 1 << QUADSPI_CR_EN_Pos // enable the peripheral 114 | ; 115 | 116 | QUADSPI->DCR = 117 | (MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2 - 3 - 1) << QUADSPI_DCR_FSIZE_Pos 118 | | (MICROPY_HW_QSPI_CS_HIGH_CYCLES - 1) << QUADSPI_DCR_CSHT_Pos 119 | | 0 << QUADSPI_DCR_CKMODE_Pos // CLK idles at low state 120 | ; 121 | 122 | QUADSPI->LPTR = 0; // raise NCS after 0 cycles of mmapped sram not in use 123 | 124 | } 125 | 126 | void qspi_memory_map(void) { 127 | // Enable memory-mapped mode 128 | 129 | QUADSPI->ABR = 0; // disable continuous read mode 130 | 131 | QUADSPI->CCR = 132 | 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled 133 | | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction 134 | | 3 << QUADSPI_CCR_FMODE_Pos // memory-mapped mode 135 | | 3 << QUADSPI_CCR_DMODE_Pos // data on 4 lines 136 | | 6 << QUADSPI_CCR_DCYC_Pos // 6 dummy cycles 137 | | 0 << QUADSPI_CCR_ABMODE_Pos // no alternate byte 138 | | QSPI_ADSIZE << QUADSPI_CCR_ADSIZE_Pos 139 | | 3 << QUADSPI_CCR_ADMODE_Pos // address on 4 lines 140 | | 3 << QUADSPI_CCR_IMODE_Pos // instruction on 4 lines 141 | | QSPI_CMD << QUADSPI_CCR_INSTRUCTION_Pos 142 | ; 143 | 144 | qspi_mpu_enable_mapped(); 145 | } 146 | 147 | STATIC int qspi_ioctl(void *self_in, uint32_t cmd) { 148 | (void)self_in; 149 | switch (cmd) { 150 | case MP_QSPI_IOCTL_INIT: 151 | qspi_init(); 152 | break; 153 | case MP_QSPI_IOCTL_BUS_ACQUIRE: 154 | // Disable memory-mapped region during bus access 155 | qspi_mpu_disable_all(); 156 | // Abort any ongoing transfer if peripheral is busy 157 | if (QUADSPI->SR & QUADSPI_SR_BUSY) { 158 | QUADSPI->CR |= QUADSPI_CR_ABORT; 159 | while (QUADSPI->CR & QUADSPI_CR_ABORT) { 160 | } 161 | } 162 | break; 163 | case MP_QSPI_IOCTL_BUS_RELEASE: 164 | // Switch to memory-map mode when bus is idle 165 | qspi_memory_map(); 166 | break; 167 | } 168 | return 0; // success 169 | } 170 | 171 | 172 | /* spi read id */ 173 | 174 | /* read id only works in spi mode, and when clock is <= 84 MHz */ 175 | 176 | STATIC uint32_t qspi_read_id() { 177 | const size_t len = 8; 178 | uint8_t cmd = SRAM_CMD_READ_ID; 179 | uint32_t spi_id[2]; 180 | 181 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 182 | 183 | QUADSPI->DLR = len - 1; // number of bytes to read 184 | 185 | QUADSPI->CCR = 186 | 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled 187 | | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction 188 | | 1 << QUADSPI_CCR_FMODE_Pos // indirect read mode 189 | | 1 << QUADSPI_CCR_DMODE_Pos // data on 1 line 190 | | 0 << QUADSPI_CCR_DCYC_Pos // 0 dummy cycles 191 | | 0 << QUADSPI_CCR_ABMODE_Pos // no alternate bytes 192 | | 2 << QUADSPI_CCR_ADSIZE_Pos // 24 bit address size 193 | | 1 << QUADSPI_CCR_ADMODE_Pos // address on 1 lines 194 | | 1 << QUADSPI_CCR_IMODE_Pos // instruction on 1 line 195 | | cmd << QUADSPI_CCR_INSTRUCTION_Pos // read opcode 196 | ; 197 | 198 | QUADSPI->AR = 0; 199 | 200 | // Wait for read to finish 201 | while (!(QUADSPI->SR & QUADSPI_SR_TCF)) { 202 | } 203 | 204 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 205 | 206 | // Read result 207 | spi_id[0] = QUADSPI->DR; 208 | spi_id[1] = QUADSPI->DR; 209 | 210 | // can only read spi id in spi mode, not in qspi mode 211 | if (!((spi_id[0] == 0) && (spi_id[1] == 0)) && !((spi_id[0] == ~0) && (spi_id[1] == ~0))) { 212 | mp_printf(MP_PYTHON_PRINTER, "psram eid: %04x %04x\n", spi_id[0], spi_id[1]); 213 | } 214 | 215 | return spi_id[0]; 216 | } 217 | 218 | 219 | STATIC void qspi_write_cmd_data(void *self_in, uint8_t cmd, size_t len, uint32_t data) { 220 | (void)self_in; 221 | 222 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 223 | 224 | if (len == 0) { 225 | QUADSPI->CCR = 226 | 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled 227 | | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction 228 | | 0 << QUADSPI_CCR_FMODE_Pos // indirect write mode 229 | | 0 << QUADSPI_CCR_DMODE_Pos // no data 230 | | 0 << QUADSPI_CCR_DCYC_Pos // 0 dummy cycles 231 | | 0 << QUADSPI_CCR_ABMODE_Pos // no alternate byte 232 | | 0 << QUADSPI_CCR_ADMODE_Pos // no address 233 | | 1 << QUADSPI_CCR_IMODE_Pos // instruction on 1 line 234 | | cmd << QUADSPI_CCR_INSTRUCTION_Pos // write opcode 235 | ; 236 | } else { 237 | QUADSPI->DLR = len - 1; 238 | 239 | QUADSPI->CCR = 240 | 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled 241 | | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction 242 | | 0 << QUADSPI_CCR_FMODE_Pos // indirect write mode 243 | | 1 << QUADSPI_CCR_DMODE_Pos // data on 1 line 244 | | 0 << QUADSPI_CCR_DCYC_Pos // 0 dummy cycles 245 | | 0 << QUADSPI_CCR_ABMODE_Pos // no alternate byte 246 | | 0 << QUADSPI_CCR_ADMODE_Pos // no address 247 | | 1 << QUADSPI_CCR_IMODE_Pos // instruction on 1 line 248 | | cmd << QUADSPI_CCR_INSTRUCTION_Pos // write opcode 249 | ; 250 | 251 | // This assumes len==2 252 | *(uint16_t *)&QUADSPI->DR = data; 253 | } 254 | 255 | // Wait for write to finish 256 | while (!(QUADSPI->SR & QUADSPI_SR_TCF)) { 257 | } 258 | 259 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 260 | } 261 | 262 | STATIC void qspi_write_qcmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, size_t len, const uint8_t *src) { 263 | (void)self_in; 264 | 265 | uint8_t adsize = MP_SPI_ADDR_IS_32B(addr) ? 3 : 2; 266 | 267 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 268 | 269 | if (len == 0) { 270 | QUADSPI->CCR = 271 | 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled 272 | | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction 273 | | 0 << QUADSPI_CCR_FMODE_Pos // indirect write mode 274 | | 0 << QUADSPI_CCR_DMODE_Pos // no data 275 | | 0 << QUADSPI_CCR_DCYC_Pos // 0 dummy cycles 276 | | 0 << QUADSPI_CCR_ABMODE_Pos // no alternate byte 277 | | adsize << QUADSPI_CCR_ADSIZE_Pos // 32/24-bit address size 278 | | 3 << QUADSPI_CCR_ADMODE_Pos // address on 4 lines 279 | | 3 << QUADSPI_CCR_IMODE_Pos // instruction on 4 lines 280 | | cmd << QUADSPI_CCR_INSTRUCTION_Pos // write opcode 281 | ; 282 | 283 | QUADSPI->AR = addr; 284 | } else { 285 | QUADSPI->DLR = len - 1; 286 | 287 | QUADSPI->CCR = 288 | 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled 289 | | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction 290 | | 0 << QUADSPI_CCR_FMODE_Pos // indirect write mode 291 | | 3 << QUADSPI_CCR_DMODE_Pos // data on 4 lines 292 | | 0 << QUADSPI_CCR_DCYC_Pos // 0 dummy cycles 293 | | 0 << QUADSPI_CCR_ABMODE_Pos // no alternate byte 294 | | adsize << QUADSPI_CCR_ADSIZE_Pos // 32/24-bit address size 295 | | 3 << QUADSPI_CCR_ADMODE_Pos // address on 4 lines 296 | | 3 << QUADSPI_CCR_IMODE_Pos // instruction on 4 lines 297 | | cmd << QUADSPI_CCR_INSTRUCTION_Pos // write opcode 298 | ; 299 | 300 | QUADSPI->AR = addr; 301 | 302 | // Write out the data 1 byte at a time 303 | while (len) { 304 | while (!(QUADSPI->SR & QUADSPI_SR_FTF)) { 305 | } 306 | *(volatile uint8_t *)&QUADSPI->DR = *src++; 307 | --len; 308 | } 309 | } 310 | 311 | // Wait for write to finish 312 | while (!(QUADSPI->SR & QUADSPI_SR_TCF)) { 313 | } 314 | 315 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 316 | } 317 | 318 | STATIC uint32_t qspi_read_cmd(void *self_in, uint8_t cmd, size_t len) { 319 | (void)self_in; 320 | 321 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 322 | 323 | QUADSPI->DLR = len - 1; // number of bytes to read 324 | 325 | QUADSPI->CCR = 326 | 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled 327 | | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction 328 | | 1 << QUADSPI_CCR_FMODE_Pos // indirect read mode 329 | | 1 << QUADSPI_CCR_DMODE_Pos // data on 1 line 330 | | 0 << QUADSPI_CCR_DCYC_Pos // 0 dummy cycles 331 | | 0 << QUADSPI_CCR_ABMODE_Pos // no alternate byte 332 | | 0 << QUADSPI_CCR_ADMODE_Pos // no address 333 | | 1 << QUADSPI_CCR_IMODE_Pos // instruction on 1 line 334 | | cmd << QUADSPI_CCR_INSTRUCTION_Pos // read opcode 335 | ; 336 | 337 | // Wait for read to finish 338 | while (!(QUADSPI->SR & QUADSPI_SR_TCF)) { 339 | } 340 | 341 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 342 | 343 | // Read result 344 | return QUADSPI->DR; 345 | } 346 | 347 | STATIC void qspi_read_qcmd_qaddr_qdata(void *self_in, uint8_t cmd, uint32_t addr, size_t len, uint8_t *dest) { 348 | (void)self_in; 349 | 350 | uint8_t adsize = MP_SPI_ADDR_IS_32B(addr) ? 3 : 2; 351 | 352 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 353 | 354 | QUADSPI->DLR = len - 1; // number of bytes to read 355 | 356 | QUADSPI->CCR = 357 | 0 << QUADSPI_CCR_DDRM_Pos // DDR mode disabled 358 | | 0 << QUADSPI_CCR_SIOO_Pos // send instruction every transaction 359 | | 1 << QUADSPI_CCR_FMODE_Pos // indirect read mode 360 | | 3 << QUADSPI_CCR_DMODE_Pos // data on 4 lines 361 | | 6 << QUADSPI_CCR_DCYC_Pos // 6 dummy cycles 362 | | 0 << QUADSPI_CCR_ABMODE_Pos // no alternate byte 363 | | adsize << QUADSPI_CCR_ADSIZE_Pos // 32 or 24-bit address size 364 | | 3 << QUADSPI_CCR_ADMODE_Pos // address on 4 lines 365 | | 3 << QUADSPI_CCR_IMODE_Pos // instruction on 4 lines 366 | | cmd << QUADSPI_CCR_INSTRUCTION_Pos // quad read opcode 367 | ; 368 | 369 | QUADSPI->ABR = 0; // alternate byte: disable continuous read mode 370 | QUADSPI->AR = addr; // addres to read from 371 | 372 | // Read in the data 4 bytes at a time if dest is aligned 373 | if (((uintptr_t)dest & 3) == 0) { 374 | while (len >= 4) { 375 | while (!(QUADSPI->SR & QUADSPI_SR_FTF)) { 376 | } 377 | *(uint32_t *)dest = QUADSPI->DR; 378 | dest += 4; 379 | len -= 4; 380 | } 381 | } 382 | 383 | // Read in remaining data 1 byte at a time 384 | while (len) { 385 | while (!((QUADSPI->SR >> QUADSPI_SR_FLEVEL_Pos) & 0x3f)) { 386 | } 387 | *dest++ = *(volatile uint8_t *)&QUADSPI->DR; 388 | --len; 389 | } 390 | 391 | QUADSPI->FCR = QUADSPI_FCR_CTCF; // clear TC flag 392 | } 393 | 394 | /* 395 | This gets picked up by drivers/memory/spiflash.c 396 | */ 397 | 398 | const mp_qspi_proto_t qspi_proto = { 399 | .ioctl = qspi_ioctl, 400 | .write_cmd_data = qspi_write_cmd_data, 401 | .write_cmd_addr_data = qspi_write_qcmd_qaddr_qdata, 402 | .read_cmd = qspi_read_cmd, 403 | .read_cmd_qaddr_qdata = qspi_read_qcmd_qaddr_qdata, 404 | }; 405 | 406 | // ----------------------------------------------------------------------------- 407 | // c interface for modules 408 | 409 | void psram_init() { 410 | qspi_init(); 411 | /* read id */ 412 | qspi_read_id(); 413 | /* set qspi mode */ 414 | qspi_write_cmd_data(NULL, SRAM_CMD_QUAD_ON, 0, 0); 415 | } 416 | 417 | void psram_read(uint32_t addr, size_t len, uint8_t *dest) { 418 | qspi_read_qcmd_qaddr_qdata(NULL, SRAM_CMD_QUAD_READ, addr, len, (void *)dest); 419 | } 420 | 421 | void psram_write(uint32_t addr, size_t len, const uint8_t *src) { 422 | qspi_write_qcmd_qaddr_qdata(NULL, SRAM_CMD_QUAD_WRITE, addr, len, (void *)src); 423 | } 424 | 425 | #endif // defined(MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2) 426 | 427 | -------------------------------------------------------------------------------- /psram.h: -------------------------------------------------------------------------------- 1 | #ifndef PSRAM_H 2 | #define PSRAM_H 3 | 4 | void psram_init(); 5 | void psram_read(uint32_t addr, size_t len, uint8_t *dest); 6 | void psram_write(uint32_t addr, size_t len, const uint8_t *src); 7 | 8 | #endif // defined(MICROPY_HW_QSPIRAM_SIZE_BITS_LOG2) 9 | 10 | --------------------------------------------------------------------------------