cancel
Showing results for 
Search instead for 
Did you mean: 

Failed to implement external loader on custom board

newbiee
Associate II

 


# External Loader Issue: STM32U5G9ZJT6Q + MX25L12833FZNI-10G (Quad-SPI)

## 1. Problem Summary

Hello, I'm a novice developer who recently started working with embedded systems. I've been struggling significantly with implementing an external loader for several weeks now...

I'm developing an external loader for **MX25L12833F** (16MB Quad-SPI NOR Flash) connected to **STM32U5G9ZJT6Q** via OCTOSPI1, but it continuously fails in STM32CubeProgrammer. The loader compiles successfully and generates a `.stldr` file, but programming operations fail.

---

## 2. Hardware Configuration

### 2.1 MCU: STM32U5G9ZJT6Q (LQFP144)
- Cortex-M33, up to 160 MHz
- 2.5MB SRAM
- OCTOSPI peripheral

### 2.2 External Flash: MX25L12833FZNI-10G (Macronix)
| Parameter | Value |
|-----------|-------|
| Capacity | 16MB (128Mbit) |
| Max Clock | 133 MHz |
| Page Size | 256 bytes |
| Sector Size | 4KB |
| Block Size | 64KB |
| Operating Voltage | 2.7V - 3.6V |

### 2.3 Pin Mapping (OCTOSPI1 Port 1)
| Function | STM32 Pin | Flash Pin |
|----------|-----------|-----------|
| CLK | PA3 | SCLK (Pin 6) |
| NCS | PA2 | CS# (Pin 1) |
| IO0 (SI) | PB1 | SI/SIO0 (Pin 5) |
| IO1 (SO) | PB0 | SO/SIO1 (Pin 2) |
| IO2 (WP#) | PA7 | WP#/SIO2 (Pin 3) |
| IO3 (HOLD#) | PA6 | HOLD#/SIO3 (Pin 7) |

**GPIO Configuration:**
- Mode: Alternate Function Push-Pull (AF10)
- Pull: Pull-up
- Speed: Very High

---

## 3. Clock Configuration

### 3.1 System Clock
```
HSE = 16 MHz (External Crystal)
PLL1:
- PLLM = 1 (VCO input = 16 MHz)
- PLLN = 10 (VCO output = 160 MHz)
- PLLR = 1 → SYSCLK = 160 MHz
- PLLQ = 4 → OSPI Clock Source = 40 MHz
```

### 3.2 OSPI Clock
```
OSPI Clock Source: PLL1Q = 40 MHz
ClockPrescaler: 4
Flash Clock: 40 MHz / 4 = 10 MHz
```

---

## 4. OCTOSPI Configuration (Loader_OCTOSPI1_Init)

```c
hospi1_local.Instance = OCTOSPI1;
hospi1_local.Init.FifoThreshold = 4;
hospi1_local.Init.DualQuad = HAL_OSPI_DUALQUAD_DISABLE;
hospi1_local.Init.MemoryType = HAL_OSPI_MEMTYPE_MACRONIX; // Macronix type
hospi1_local.Init.DeviceSize = 24; // 2^24 = 16MB
hospi1_local.Init.ChipSelectHighTime = 2; // tSHSL >= 30ns
hospi1_local.Init.FreeRunningClock = HAL_OSPI_FREERUNCLK_DISABLE;
hospi1_local.Init.ClockMode = HAL_OSPI_CLOCK_MODE_0; // Mode 0
hospi1_local.Init.WrapSize = HAL_OSPI_WRAP_NOT_SUPPORTED;
hospi1_local.Init.ClockPrescaler = 4; // 40MHz / 4 = 10MHz
hospi1_local.Init.SampleShifting = HAL_OSPI_SAMPLE_SHIFTING_NONE;
hospi1_local.Init.DelayHoldQuarterCycle = HAL_OSPI_DHQC_DISABLE;
hospi1_local.Init.ChipSelectBoundary = 0;
hospi1_local.Init.DelayBlockBypass = HAL_OSPI_DELAY_BLOCK_BYPASSED;
hospi1_local.Init.MaxTran = 0;
hospi1_local.Init.Refresh = 0;

// OSPI Manager Configuration
sOspiManagerCfg.ClkPort = 1;
sOspiManagerCfg.NCSPort = 1;
sOspiManagerCfg.IOLowPort = HAL_OSPIM_IOPORT_1_LOW;
```

---

## 5. Flash Commands Used (MX25L12833F Datasheet)

| Command | Opcode | Mode | Description |
|---------|--------|------|-------------|
| Reset Enable | 0x66 | 1-0-0 | Enable software reset |
| Reset Memory | 0x99 | 1-0-0 | Execute software reset |
| Read Status Register | 0x05 | 1-0-1 | Read SR (WIP, WEL, QE) |
| Read Config Register | 0x15 | 1-0-1 | Read configuration |
| Write Enable | 0x06 | 1-0-0 | Enable write operations |
| Write Status/Config | 0x01 | 1-0-1 | Write SR + CR (2 bytes) |
| 4READ (Quad I/O Read) | 0xEB | 1-4-4 | Quad fast read |
| 4PP (Quad Page Program) | 0x38 | 1-4-4 | Quad page program |
| Block Erase 64KB | 0xD8 | 1-1-0 | 64KB block erase |
| Chip Erase | 0xC7 | 1-0-0 | Full chip erase |

### 5.1 Status Register Bit Definitions
- Bit 0 (WIP): Write In Progress
- Bit 1 (WEL): Write Enable Latch
- Bit 6 (QE): Quad Enable

---

## 6. Key Implementation Details

### 6.1 Init() Function Flow
```
1. memset(&hospi1_local, 0, ...) - Clear OSPI handle
2. HAL_Init()
3. Loader_SystemClock_Config() - Setup HSE + PLL
4. Loader_GPIO_Init() - Enable GPIO clocks
5. Loader_OCTOSPI1_Init() - Configure OCTOSPI1
6. OSPI_ResetMemory() - Send 0x66 + 0x99
7. OSPI_QuadMode(1) - Set QE bit in Status Register
8. OSPI_EnterMemoryMappedMode() - Enter memory-mapped mode
```

### 6.2 Memory-Mapped Mode Configuration (4READ: 0xEB)

```c
// Read Configuration
sCommand.OperationType = HAL_OSPI_OPTYPE_READ_CFG;
sCommand.Instruction = 0xEB; // 4READ
sCommand.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE; // 1-line command
sCommand.AddressMode = HAL_OSPI_ADDRESS_4_LINES; // 4-line address
sCommand.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
sCommand.AlternateBytesMode = HAL_OSPI_ALTERNATE_BYTES_NONE; // MACRONIX type handles this
sCommand.DataMode = HAL_OSPI_DATA_4_LINES; // 4-line data
sCommand.DummyCycles = 6; // Per datasheet Table 10
sCommand.DQSMode = HAL_OSPI_DQS_DISABLE;
sCommand.SIOOMode = HAL_OSPI_SIOO_INST_EVERY_CMD;

// Write Configuration (for memory-mapped write)
sCommand.OperationType = HAL_OSPI_OPTYPE_WRITE_CFG;
sCommand.Instruction = 0x38; // 4PP
sCommand.DummyCycles = 0; // No dummy for write

// Enable memory-mapped mode
sMemMappedCfg.TimeOutActivation = HAL_OSPI_TIMEOUT_COUNTER_DISABLE;
HAL_OSPI_MemoryMapped(&hospi1_local, &sMemMappedCfg);
```

### 6.3 Quad Enable Sequence

```c
// 1. Read Status Register (0x05) → reg[0]
// 2. Read Config Register (0x15) → reg[1]
// 3. Check if QE (bit 6) is already set → skip if set
// 4. Set QE bit: reg[0] |= 0x40
// 5. Write Enable (0x06)
// 6. Write Status + Config Register (0x01) with 2 bytes [SR, CR]
// 7. Poll WIP until write complete
```

### 6.4 Write Function (4PP: 0x38)

```c
// 4PP Command Configuration (per datasheet Figure 49)
sCommand.Instruction = 0x38; // 4PP
sCommand.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE; // 1-line command
sCommand.AddressMode = HAL_OSPI_ADDRESS_4_LINES; // 4-line address
sCommand.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
sCommand.DataMode = HAL_OSPI_DATA_4_LINES; // 4-line data
sCommand.DummyCycles = 0; // No dummy for program
```

### 6.5 Sector Erase (Block Erase 64KB: 0xD8)

```c
// Standard SPI mode for erase command
sCommand.Instruction = 0xD8; // BE 64KB
sCommand.InstructionMode = HAL_OSPI_INSTRUCTION_1_LINE;
sCommand.AddressMode = HAL_OSPI_ADDRESS_1_LINE; // Standard SPI
sCommand.AddressSize = HAL_OSPI_ADDRESS_24_BITS;
sCommand.DataMode = HAL_OSPI_DATA_NONE;
```

---

## 7. HAL Tick Override (External Loader Specific)

Since external loaders run without SysTick, HAL timing functions are overridden:

```c
volatile uint32_t uwTick_local = 0;

HAL_StatusTypeDef HAL_InitTick(uint32_t TickPriority)
{
(void)TickPriority;
return HAL_OK;
}

uint32_t HAL_GetTick(void)
{
return uwTick_local++; // Simple increment for timeout purposes
}
```

---

## 8. Linker Script Configuration

```
MEMORY
{
INFO (r) : ORIGIN = 0x00000000, LENGTH = 0x400 /* StorageInfo at address 0 */
RAM (xrw) : ORIGIN = 0x20000004, LENGTH = 0x27FFC /* Code in SRAM */
}

SECTIONS
{
.storage_info 0x00000000 :
{
. = ALIGN(4);
KEEP(*(.storage_info))
KEEP(*Dev_Inf.o(.rodata*))
. = ALIGN(4);
} >INFO

.text : { ... } >RAM
}
```

---

## 9. StorageInfo Structure (Dev_Inf.c)

```c
struct StorageInfo const StorageInfo = {
"MX25L12833F_STM32U5G9_CustomBoard", // Device Name
NOR_FLASH, // Device Type = 3
0x90000000, // Device Start Address
0x01000000, // Device Size: 16MB
0x00000100, // Page Size: 256 bytes
0xFF, // Erased Memory Content
{
{ 0x00000100, 0x00010000 }, // 256 sectors × 64KB = 16MB
{ 0x00000000, 0x00000000 }, // End marker
...
}
};
```

---

## 10. Build Configuration

- **Toolchain:** GNU Tools for STM32 (13.3.rel1)
- **Linker Flags:** `-nostartfiles -static --specs=nano.specs`
- **Output Format:** .stldr (ELF format)
- **FPU:** FPv5-SP-D16, Hard float ABI

---

## 11. Questions / Areas of Concern

1. **Dummy Cycles for 4READ (0xEB):**
- Using 6 dummy cycles as per datasheet Table 10 (DC[1:0]=00 default)
- Is HAL_OSPI_MEMTYPE_MACRONIX correctly handling mode bits?

2. **GPIO Pull Configuration:**
- Currently using GPIO_PULLUP on all OSPI pins
- Should WP# (IO2) and HOLD# (IO3) have specific handling?

3. **Clock Timing:**
- Operating at 10 MHz (40 MHz / 4)
- Flash supports up to 133 MHz for 4READ

4. **Memory-Mapped Mode Alternate Bytes:**
- Currently set to HAL_OSPI_ALTERNATE_BYTES_NONE
- Should mode bits (0x00) be explicitly configured?

5. **StorageInfo Structure:**
- Using NOR_FLASH (3) as device type
- Sector configuration: 256 × 64KB blocks

---

## 12. Error Behavior

When attempting to use the external loader in STM32CubeProgrammer:

1. When reading data in STM32CubeProgrammer, it sometimes succeeds and sometimes fails.

2. When erasing sectors in STM32CubeProgrammer, it sometimes succeeds and sometimes fails.

3. Writing data in STM32CubeProgrammer sometimes succeeds and sometimes fails. Furthermore, when data writing succeeds, reading the data afterward reveals that the data was not actually written.

---

## 13. Files Attached

core folder
- `Loader_Src.c` - Main loader implementation
- `Dev_Inf.c` / `Dev_Inf.h` - Device information structure
- `octospi.c` - OCTOSPI peripheral configuration

root
- `STM32U5G9xx_flash.ld` - Linker script
- `MX25L12833F_Loader.ioc` - STM32CubeMX configuration

debug floder
- MX25L12833F_Loader.stldr
- result.txt - objdump output

- `Circuit Diagram` - Designed to support both HSPI and OCTOSPI since it's a component being used for the first time

---

## 14. What I've Tried

1. Multiple dummy cycle values (4, 6, 8)
2. Different clock prescalers (1, 2, 4)
3. SampleShifting ON/OFF
4. HAL_OSPI_MEMTYPE_MACRONIX vs HAL_OSPI_MEMTYPE_MICRON
5. GPIO_PULLUP vs GPIO_NOPULL
6. DelayBlockBypass enabled/disabled

---

Is the problem with the code, the hardware configuration, or the physical circuit?

If there is anything else you need besides the attached materials, please let me know.

Any guidance on potential issues or suggestions for debugging would be greatly appreciated!

 

compiler setting.png

GCC Linker setting.png

2 REPLIES 2
newbiee
Associate II

Additionally, all GPIO pins were initially set to ‘No pull-up and no pull-down,’ and at that time, I never received any notification of success.
After modifying all GPIO pins to ‘Pull-up,’ the state became such that sometimes it succeeded and sometimes it failed.

Andrew Neil
Super User

Please see How to insert source code for how to properly include source code in posts

See also How to insert formatted text which is not source code - eg, your tables.

 

What testing have you done on your custom hardware?

 

How to write your question to maximize your chances to find a solution

A complex system that works is invariably found to have evolved from a simple system that worked.
A complex system designed from scratch never works and cannot be patched up to make it work.