diff --git a/stmhal/Makefile b/stmhal/Makefile
index 9a9c50f065b22d33d2b2db00b774cb048357894b..87236068ba8921877358fa1ac5260d587f7f06e4 100644
--- a/stmhal/Makefile
+++ b/stmhal/Makefile
@@ -117,6 +117,10 @@ SRC_LIB = $(addprefix lib/,\
 	utils/pyexec.c \
 	)
 
+DRIVERS_SRC_C = $(addprefix drivers/,\
+	memory/spiflash.c \
+	)
+
 SRC_C = \
 	main.c \
 	system_stm32.c \
@@ -256,6 +260,7 @@ endif
 OBJ =
 OBJ += $(PY_O)
 OBJ += $(addprefix $(BUILD)/, $(SRC_LIB:.c=.o))
+OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o))
 OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
 OBJ += $(addprefix $(BUILD)/, $(SRC_O))
 OBJ += $(addprefix $(BUILD)/, $(SRC_HAL:.c=.o))
diff --git a/stmhal/storage.c b/stmhal/storage.c
index 14b504d7162ef695774206fda3a39bf2af0e63b9..60a7e9f480c3c75cd379c516b8eb7dc5125b5371 100644
--- a/stmhal/storage.c
+++ b/stmhal/storage.c
@@ -38,6 +38,14 @@
 #include "storage.h"
 #include "irq.h"
 
+#if defined(MICROPY_HW_SPIFLASH_SIZE_BITS)
+#define USE_INTERNAL (0)
+#else
+#define USE_INTERNAL (1)
+#endif
+
+#if USE_INTERNAL
+
 #if defined(STM32F405xx) || defined(STM32F415xx) || defined(STM32F407xx)
 
 #define CACHE_MEM_START_ADDR (0x10000000) // CCM data RAM, 64k
@@ -158,19 +166,50 @@ static uint8_t *flash_cache_get_addr_for_read(uint32_t flash_addr) {
     return (uint8_t*)flash_addr;
 }
 
+#else
+
+#include "drivers/memory/spiflash.h"
+#include "genhdr/pins.h"
+
+#define FLASH_PART1_START_BLOCK (0x100)
+#define FLASH_PART1_NUM_BLOCKS (MICROPY_HW_SPIFLASH_SIZE_BITS / 8 / FLASH_BLOCK_SIZE)
+
+static bool flash_is_initialised = false;
+
+STATIC const mp_spiflash_t spiflash = {
+    .cs = &MICROPY_HW_SPIFLASH_CS,
+    .spi = {
+        .base = {&mp_machine_soft_spi_type},
+        .delay_half = MICROPY_PY_MACHINE_SPI_MIN_DELAY,
+        .polarity = 0,
+        .phase = 0,
+        .sck = &MICROPY_HW_SPIFLASH_SCK,
+        .mosi = &MICROPY_HW_SPIFLASH_MOSI,
+        .miso = &MICROPY_HW_SPIFLASH_MISO,
+    },
+};
+
+#endif
+
 void storage_init(void) {
     if (!flash_is_initialised) {
+        #if USE_INTERNAL
         flash_flags = 0;
         flash_cache_sector_id = 0;
         flash_tick_counter_last_write = 0;
+        #else
+        mp_spiflash_init((mp_spiflash_t*)&spiflash);
+        #endif
         flash_is_initialised = true;
     }
 
+    #if USE_INTERNAL
     // Enable the flash IRQ, which is used to also call our storage IRQ handler
     // It needs to go at a higher priority than all those components that rely on
     // the flash storage (eg higher than USB MSC).
     HAL_NVIC_SetPriority(FLASH_IRQn, IRQ_PRI_FLASH, IRQ_SUBPRI_FLASH);
     HAL_NVIC_EnableIRQ(FLASH_IRQn);
+    #endif
 }
 
 uint32_t storage_get_block_size(void) {
@@ -182,6 +221,8 @@ uint32_t storage_get_block_count(void) {
 }
 
 void storage_irq_handler(void) {
+    #if USE_INTERNAL
+
     if (!(flash_flags & FLASH_FLAG_DIRTY)) {
         return;
     }
@@ -222,10 +263,14 @@ void storage_irq_handler(void) {
         // indicate a clean cache with LED off
         led_state(PYB_LED_R1, 0);
     }
+
+    #endif
 }
 
 void storage_flush(void) {
+    #if USE_INTERNAL
     flash_cache_flush();
+    #endif
 }
 
 static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_block, uint32_t num_blocks) {
@@ -264,6 +309,8 @@ static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_blo
     buf[15] = num_blocks >> 24;
 }
 
+#if USE_INTERNAL
+
 static uint32_t convert_block_to_flash_addr(uint32_t block) {
     if (FLASH_PART1_START_BLOCK <= block && block < FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS) {
         // a block in partition 1
@@ -279,6 +326,8 @@ static uint32_t convert_block_to_flash_addr(uint32_t block) {
     return -1;
 }
 
+#endif
+
 bool storage_read_block(uint8_t *dest, uint32_t block) {
     //printf("RD %u\n", block);
     if (block == 0) {
@@ -299,6 +348,8 @@ bool storage_read_block(uint8_t *dest, uint32_t block) {
         return true;
 
     } else {
+        #if USE_INTERNAL
+
         // non-MBR block, get data from flash memory, possibly via cache
         uint32_t flash_addr = convert_block_to_flash_addr(block);
         if (flash_addr == -1) {
@@ -308,6 +359,27 @@ bool storage_read_block(uint8_t *dest, uint32_t block) {
         uint8_t *src = flash_cache_get_addr_for_read(flash_addr);
         memcpy(dest, src, FLASH_BLOCK_SIZE);
         return true;
+
+        #else
+
+        // non-MBR block, get data from SPI flash
+
+        if (block < FLASH_PART1_START_BLOCK || block >= FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS) {
+            // bad block number
+            return false;
+        }
+
+        // we must disable USB irqs to prevent MSC contention with SPI flash
+        uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS);
+
+        mp_spiflash_read((mp_spiflash_t*)&spiflash,
+            (block - FLASH_PART1_START_BLOCK) * FLASH_BLOCK_SIZE, FLASH_BLOCK_SIZE, dest);
+
+        restore_irq_pri(basepri);
+
+        return true;
+
+        #endif
     }
 }
 
@@ -318,6 +390,8 @@ bool storage_write_block(const uint8_t *src, uint32_t block) {
         return true;
 
     } else {
+        #if USE_INTERNAL
+
         // non-MBR block, copy to cache
         uint32_t flash_addr = convert_block_to_flash_addr(block);
         if (flash_addr == -1) {
@@ -327,6 +401,27 @@ bool storage_write_block(const uint8_t *src, uint32_t block) {
         uint8_t *dest = flash_cache_get_addr_for_write(flash_addr);
         memcpy(dest, src, FLASH_BLOCK_SIZE);
         return true;
+
+        #else
+
+        // non-MBR block, write to SPI flash
+
+        if (block < FLASH_PART1_START_BLOCK || block >= FLASH_PART1_START_BLOCK + FLASH_PART1_NUM_BLOCKS) {
+            // bad block number
+            return false;
+        }
+
+        // we must disable USB irqs to prevent MSC contention with SPI flash
+        uint32_t basepri = raise_irq_pri(IRQ_PRI_OTG_FS);
+
+        int ret = mp_spiflash_write((mp_spiflash_t*)&spiflash,
+            (block - FLASH_PART1_START_BLOCK) * FLASH_BLOCK_SIZE, FLASH_BLOCK_SIZE, src);
+
+        restore_irq_pri(basepri);
+
+        return ret == 0;
+
+        #endif
     }
 }