STM32 + Micropython + Read/Write W25Q128

How to Run

mpremote exec "import flashRead_w25; flashRead_w25.dump_flash(0, 1280)"
mpremote exec "import flashWrite_w25; flashWrite_w25.write('w 2 0x23 3 0x45')"

Write

import machine
import time
import ssd1306
from machine import SPI, Pin

# W25Q128 SPI Flash Configuration
# W25Q128 has 16MB (16777216 bytes) = 128 Mbit
# Page size: 256 bytes
# Sector size: 4KB
# Block size: 64KB

# W25Q128 Commands
CMD_WRITE_ENABLE = 0x06
CMD_WRITE_DISABLE = 0x04
CMD_READ_STATUS = 0x05
CMD_READ_STATUS2 = 0x35
CMD_READ_STATUS3 = 0x15
CMD_WRITE_STATUS = 0x01
CMD_READ_DATA = 0x03
CMD_PAGE_PROGRAM = 0x02
CMD_SECTOR_ERASE = 0x20
CMD_BLOCK_ERASE_32K = 0x52
CMD_BLOCK_ERASE_64K = 0xD8
CMD_CHIP_ERASE = 0xC7
CMD_READ_ID = 0x9F
CMD_POWER_DOWN = 0xB9
CMD_RELEASE_POWER_DOWN = 0xAB
CMD_RESET_ENABLE = 0x66
CMD_RESET_MEMORY = 0x99

# SPI Configuration
spi = None
cs = None

def init_spi():
    global spi, cs
    # Initialize SPI bus (SPI1) for WeAct BlackPill
    # SCK=PA5, MISO=PA6, MOSI=PA7
    try:
        spi = machine.SPI(
            1,
            baudrate=1000000,
            polarity=0,
            phase=0,
            bits=8,
            firstbit=machine.SPI.MSB,
            sck=machine.Pin('A5'),
            mosi=machine.Pin('A7'),
            miso=machine.Pin('A6'),
        )
    except (ValueError, TypeError):
        try:
            spi = machine.SPI(1, baudrate=1000000, polarity=0, phase=0)
        except Exception:
            spi = machine.SoftSPI(
                baudrate=500000,
                polarity=0,
                phase=0,
                sck=machine.Pin('A5'),
                mosi=machine.Pin('A7'),
                miso=machine.Pin('A6'),
            )

    # CS pin (adjust based on your wiring)
    cs = machine.Pin('A4', machine.Pin.OUT, value=1)
    time.sleep_ms(1)

    flash_wake()
    flash_reset()

    disable_protection()
    
    # Check device ID
    device_id = read_device_id()
    if device_id == 0x000000:
        time.sleep_ms(5)
        device_id = read_device_id()
    print(f"W25Q128 Device ID: {device_id:06X}")
    if device_id == 0xEF4018:
        print("W25Q128 detected successfully")
    else:
        print(f"Warning: Unexpected device ID: {device_id:06X} (expected 0xEF4018)")

def read_device_id():
    """Read W25Q128 manufacturer and device ID"""
    cs.value(0)
    spi.write(bytes([CMD_READ_ID]))
    id_data = spi.read(3)
    cs.value(1)
    return (id_data[0] << 16) | (id_data[1] << 8) | id_data[2]

def flash_wake():
    """Release from power-down (safe to call even if not asleep)"""
    cs.value(0)
    spi.write(bytes([CMD_RELEASE_POWER_DOWN]))
    cs.value(1)
    time.sleep_ms(1)

def flash_reset():
    """Reset the flash (W25Q series supports 0x66/0x99)"""
    cs.value(0)
    spi.write(bytes([CMD_RESET_ENABLE]))
    cs.value(1)
    time.sleep_us(50)
    cs.value(0)
    spi.write(bytes([CMD_RESET_MEMORY]))
    cs.value(1)
    time.sleep_ms(1)

def read_status():
    """Read status register"""
    cs.value(0)
    spi.write(bytes([CMD_READ_STATUS]))
    status = spi.read(1)[0]
    cs.value(1)
    return status

def read_status2():
    """Read status register-2"""
    cs.value(0)
    spi.write(bytes([CMD_READ_STATUS2]))
    status = spi.read(1)[0]
    cs.value(1)
    return status

def read_status3():
    """Read status register-3"""
    cs.value(0)
    spi.write(bytes([CMD_READ_STATUS3]))
    status = spi.read(1)[0]
    cs.value(1)
    return status

def write_status(sr1, sr2):
    """Write status register-1 and -2"""
    wait_busy()
    write_enable()
    cs.value(0)
    spi.write(bytes([CMD_WRITE_STATUS, sr1 & 0xFF, sr2 & 0xFF]))
    cs.value(1)
    wait_busy()

def disable_protection():
    """Clear block protection bits and SRP"""
    sr1 = read_status()
    sr2 = read_status2()
    sr3 = read_status3()
    if (sr1 & 0x1C) or (sr1 & 0x80):
        print(f"Status before: SR1={sr1:02X} SR2={sr2:02X} SR3={sr3:02X}")
        write_status(sr1 & ~0x9C, sr2 & ~0x40)
        sr1 = read_status()
        sr2 = read_status2()
        sr3 = read_status3()
        print(f"Status after:  SR1={sr1:02X} SR2={sr2:02X} SR3={sr3:02X}")

def wait_busy():
    """Wait until write operation completes"""
    while read_status() & 0x01:
        time.sleep_us(10)

def write_enable():
    """Enable write operations"""
    cs.value(0)
    spi.write(bytes([CMD_WRITE_ENABLE]))
    cs.value(1)

def write_disable():
    """Disable write operations"""
    cs.value(0)
    spi.write(bytes([CMD_WRITE_DISABLE]))
    cs.value(1)

def read_byte(addr):
    """Read a single byte from address"""
    cs.value(0)
    spi.write(bytes([CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    data = spi.read(1)[0]
    cs.value(1)
    return data

def read_bytes(addr, length):
    """Read multiple bytes from address"""
    cs.value(0)
    spi.write(bytes([CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    data = spi.read(length)
    cs.value(1)
    return data

def write_page(addr, data):
    """Write up to 256 bytes (one page). Address must be page-aligned."""
    if len(data) > 256:
        raise ValueError("Page write data must be <= 256 bytes")
    
    wait_busy()
    write_enable()

    # Ensure write-enable latch is set (bit 1)
    if (read_status() & 0x02) == 0:
        write_enable()
        if (read_status() & 0x02) == 0:
            raise RuntimeError("Write enable latch not set. Check /WP pin.")
    
    cs.value(0)
    spi.write(bytes([CMD_PAGE_PROGRAM, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    spi.write(data)
    cs.value(1)
    
    wait_busy()

def write_byte(addr, value):
    """Write a single byte to address"""
    write_page(addr, bytes([value & 0xFF]))

def sector_erase(addr):
    """Erase a 4KB sector (sector address must be sector-aligned)"""
    wait_busy()
    write_enable()

    if (read_status() & 0x02) == 0:
        write_enable()
        if (read_status() & 0x02) == 0:
            raise RuntimeError("Write enable latch not set. Check /WP pin.")
    
    cs.value(0)
    spi.write(bytes([CMD_SECTOR_ERASE, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    cs.value(1)
    
    wait_busy()

def block_erase_64k(addr):
    """Erase a 64KB block (address must be block-aligned)"""
    wait_busy()
    write_enable()
    
    cs.value(0)
    spi.write(bytes([CMD_BLOCK_ERASE_64K, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    cs.value(1)
    
    wait_busy()

def chip_erase():
    """Erase entire chip (takes several seconds)"""
    wait_busy()
    write_enable()
    
    cs.value(0)
    spi.write(bytes([CMD_CHIP_ERASE]))
    cs.value(1)
    
    print("Chip erase started (this may take 10-30 seconds)...")
    wait_busy()
    print("Chip erase complete")

def write_00_to_ff():
    init_spi()
    
    # i2c_display = machine.I2C(1)
    # display = ssd1306.SSD1306_I2C(128, 64, i2c_display)
    # display.fill(0)
    # display.text("W25Q128 Writer", 5, 5, 1)
    # display.show()

    # Erase first 64KB (16 sectors) before writing
    # for sector in range(0, 16):
    #     sector_addr = sector * 0x1000
    #     print(f"E {sector_addr:06X}")
    #     sector_erase(sector_addr)

    # Write first 64KB for testing, page-by-page
    for page_addr in range(0, 65536, 256):
        if page_addr % 1000 == 0:
            print(f"W {page_addr:06X}")
            # display.fill(0)
            # display.text("W25Q128 Writer", 5, 5, 1)
            # display.text(f"W {page_addr:06X}", 5, 30, 1)
            # display.show()

        page = bytes([(page_addr + i) & 0xFF for i in range(256)])
        write_page(page_addr, page)

    # display.fill(0)
    # display.text("W25Q128 Writer", 5, 5, 1)
    # display.text(f"Write Complete", 5, 30, 1)
    # display.show()


def write(str):
    init_spi()
    
    # Parse the input string into address-value pairs and write each value
    str = str[2:]  # Remove "w " prefix
    tokens = str.strip().split()
    if len(tokens) % 2 != 0:
        raise ValueError("Input string must contain pairs of <addr> <value>")
    
    # i2c_display = machine.I2C(1)
    # display = ssd1306.SSD1306_I2C(128, 64, i2c_display)
    # display.fill(0)
    # display.text("W25Q128 Writer", 5, 5, 1)
    # display.show()

    for i in range(0, len(tokens), 2):
        addr = int(tokens[i], 0)  # Support hex (0x...), decimal, etc.
        value = int(tokens[i+1], 0)
        write_byte(addr, value)

        if addr > 0 and addr % 1000 == 0:
            print(f"Wrote {value:02X} to {addr:06X}")
            # display.fill(0)
            # display.text("W25Q128 Writer", 5, 5, 1)
            # display.text(f"W {value:02X} to {addr:06X}", 5, 30, 1)
            # display.show()

    # display.fill(0)
    # display.text("W25Q128 Writer", 5, 5, 1)
    # display.text(f"W {value:02X} to {addr:06X}", 5, 30, 1)
    # display.show()


def erase():
    init_spi()
    
    print("Erasing entire W25Q128 chip...")
    chip_erase()
    print("Erase complete")


def erase_sector(sector_addr):
    """Erase a specific 4KB sector"""
    init_spi()
    
    # Align to sector boundary (4KB = 0x1000)
    sector_addr = sector_addr & 0xFFFFF000
    print(f"Erasing sector at {sector_addr:06X}...")
    sector_erase(sector_addr)
    print(f"Sector at {sector_addr:06X} erased")


if __name__ == "__main__":
    write_00_to_ff()

READ:

import machine
import time
import ssd1306
from machine import SPI, Pin

# W25Q128 SPI Flash Configuration
# W25Q128 has 16MB (16777216 bytes) = 128 Mbit

# W25Q128 Commands
CMD_READ_DATA = 0x03
CMD_READ_STATUS = 0x05
CMD_READ_ID = 0x9F
CMD_FAST_READ = 0x0B
CMD_POWER_DOWN = 0xB9
CMD_RELEASE_POWER_DOWN = 0xAB
CMD_RESET_ENABLE = 0x66
CMD_RESET_MEMORY = 0x99

# SPI Configuration
spi = None
cs = None

def init_spi():
    global spi, cs
    # Initialize SPI bus (SPI1) for WeAct BlackPill
    # SCK=PA5, MISO=PA6, MOSI=PA7
    try:
        spi = machine.SPI(
            1,
            baudrate=1000000,
            polarity=0,
            phase=0,
            bits=8,
            firstbit=machine.SPI.MSB,
            sck=machine.Pin('A5'),
            mosi=machine.Pin('A7'),
            miso=machine.Pin('A6'),
        )
    except (ValueError, TypeError):
        try:
            spi = machine.SPI(1, baudrate=1000000, polarity=0, phase=0)
        except Exception:
            spi = machine.SoftSPI(
                baudrate=500000,
                polarity=0,
                phase=0,
                sck=machine.Pin('A5'),
                mosi=machine.Pin('A7'),
                miso=machine.Pin('A6'),
            )

    # CS pin (adjust based on your wiring)
    cs = machine.Pin('A4', machine.Pin.OUT, value=1)
    time.sleep_ms(1)

    flash_wake()
    flash_reset()
    
    # Check device ID
    device_id = read_device_id()
    if device_id == 0x000000:
        time.sleep_ms(5)
        device_id = read_device_id()
    print(f"W25Q128 Device ID: {device_id:06X}")
    if device_id == 0xEF4018:
        print("W25Q128 detected successfully")
    else:
        print(f"Warning: Unexpected device ID: {device_id:06X} (expected 0xEF4018)")

def read_device_id():
    """Read W25Q128 manufacturer and device ID"""
    cs.value(0)
    spi.write(bytes([CMD_READ_ID]))
    id_data = spi.read(3)
    cs.value(1)
    return (id_data[0] << 16) | (id_data[1] << 8) | id_data[2]

def flash_wake():
    """Release from power-down (safe to call even if not asleep)"""
    cs.value(0)
    spi.write(bytes([CMD_RELEASE_POWER_DOWN]))
    cs.value(1)
    time.sleep_ms(1)

def flash_reset():
    """Reset the flash (W25Q series supports 0x66/0x99)"""
    cs.value(0)
    spi.write(bytes([CMD_RESET_ENABLE]))
    cs.value(1)
    time.sleep_us(50)
    cs.value(0)
    spi.write(bytes([CMD_RESET_MEMORY]))
    cs.value(1)
    time.sleep_ms(1)

def read_status():
    """Read status register"""
    cs.value(0)
    spi.write(bytes([CMD_READ_STATUS]))
    status = spi.read(1)[0]
    cs.value(1)
    return status

def read_byte(addr):
    """Read a single byte from address"""
    cs.value(0)
    spi.write(bytes([CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    data = spi.read(1)[0]
    cs.value(1)
    return data

def read_bytes(addr, length):
    """Read multiple bytes from address"""
    cs.value(0)
    spi.write(bytes([CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF]))
    data = spi.read(length)
    cs.value(1)
    return data

def dump_flash(start, length):
    """Dump W25Q128 flash contents"""
    init_spi()
    
    # Initialize display (optional)
    # i2c_display = machine.I2C(1)
    # display = ssd1306.SSD1306_I2C(128, 64, i2c_display)
    # display.fill(0)
    # display.text("W25Q128 Reader", 5, 5, 1)
    # display.show()
    
    for base in range(start, start + length, 16):
        row = [f"{base:06X}:"]

        # if base % 1000 == 0:
        #     display.fill(0)
        #     display.text("W25Q128 Reader", 5, 5, 1)
        #     display.text(f"R {base:06X}", 5, 30, 1)
        #     display.show()

        chunk_len = min(16, (start + length) - base)
        data = read_bytes(base, chunk_len)
        for b in data:
            row.append(f"{b:02X}")
        print(' '.join(row))

if __name__ == "__main__":
    # W25Q128 has 16MB (16777216 bytes)
    # Read first 64KB for testing
    dump_flash(0, 65536)