cancel
Showing results for 
Search instead for 
Did you mean: 

How to port an STM32N6 XSPI memory-mapped DTR example from MX66UW1G45G to IS25LXWX01G

Summary

In this article, we describe the modifications required to port the STM32N6570 example on the STM32N6570-DK board, which uses the MX66UW1G45G memory from Macronix, to the IS25LXWX01G memory from ISSI.

The example that we use describes how to erase a part of an XSPI NOR memory, write data in indirect mode, and access to XSPI NOR memory in memory-mapped mode to check the data. This example is available within STM32CubeN6 under the following path:

STM32Cube_FW_N6_V1.3.0\Projects\STM32N6570-DK\Examples\XSPI

The example is available here on GitHub as well.

1. Hardware considerations

The IS25LXWX01G and MX66UW1G45G memories are pin-to-pin compatible, and both operate at 1.8 V, making it straightforward to swap the memories.

The extended serial peripheral interface (XSPI) has 16 I/O data lines and supports single, dual, quad-SPI, octo-SPI, and 16-bit memories so that a wide range of external memory types can be used. The STM32N6570-DK board is configured to use octal mode, where eight data lines (IO0 through IO7) are connected to the external memory, as illustrated below:

 
AMEMCUSupportCenterBB_26-1766171846191.png

 

Impedance for the high-speed signals between the microcontroller and memory device is important for reliability and should be adjusted based on implementation. This topic is beyond the scope of this article.

2. Software part

The XSPI section in the reference manual (RM0486) shows the different types of memory supported and configured using bitfield MTYP[2:0] in the XSPI_DCR1 register. Data ordering is of particular importance when configuring the interface:

  • D0/D1 data ordering: In this configuration, Data 0 is output first, followed by Data 1, as illustrated below. In this case, the DQS signal polarity is opposite to the clock. AMEMCUSupportCenterBB_27-1766172023576.png
  • D1/D0 data ordering: In this configuration, Data 1 is output first, followed by Data 0, as illustrated below. In this case, the DQS signal polarity matches the clock. AMEMCUSupportCenterBB_29-1766172227843.png

     

 

According to the IS25LXWX01G datasheet section READ MEMORY Operations Timings, the data is output in the order D0 followed by D1, and the DQS signal polarity is opposite to the clock, as shown below: excerpt from the IS25LXWX01G datasheet.

 
AMEMCUSupportCenterBB_30-1766172974610.png

 

 

This indicates that the IS25LXWX01G is compatible with Micron mode, and the MTYP[2:0] bits in XSPI_DCR1 must be configured to 0b000: Micron Mode.

To implement this change, open STM32CubeMX and load XSPI_NOR_MemoryMapped_DTR.ioc available under:
STM32Cube_FW_N6_V1.3.0\Projects\STM32N6570-DK\Examples\XSPI\XSPI_NOR_MemoryMapped_DTR.

In XSPI2 in Connectivity category under Pinout & Configuration, then under Parameter Settings tab, change the memory type from Macronix to Micron, as shown below:

 
AMEMCUSupportCenterBB_0-1766254011435.png

 

At this point, save the project and generate the code by clicking the [Generate Code] button located in the top-right corner.

3. Firmware part

3.1 Editing main.h

Each memory supplier uses unique commands to communicate with external serial memory. Table 8.1 Command Set of IS25LXWX01G datasheet lists all the commands supported by the memory. The extract below shows the read commands.

The first step is to modify the commands defined in Projects\STM32N6570-DK\Examples\XSPI\XSPI_NOR_MemoryMapped_DTR\FSBL\Inc\main.h to align with the IS25LXWX01G datasheet.

The first command OCTAL_IO_READ_CMD is not utilized, simply remove it.

The second command is OCTAL_IO_DTR_READ_CMD, which initiates a read operation from an external memory device using the Octal I/O interface in Double Transfer Rate (DTR) mode.

Based on the excerpt below from IS25LXWX01G datasheet Table 8.1 Command Set, the memory supports many read commands. For best performance, we want to use one that supports octal mode and Double Data Rate (DDR) which is the same as Double Transfer Rate (DTR).

The two commands that satisfy the two requirements are DDR OCTAL OUTPUT FAST READ (9Dh) and DDR OCTAL I/O FAST READ (FDh). Using the command DDR OCTAL I/O FAST READ (FDh) has the advantage of not requiring to switch from 3-byte address mode to 4-byte address mode.  Later in this article I show how to switch the addressing mode even though we are enabling the Octal DTR mode with the command E7h.

AMEMCUSupportCenterBB_1-1766254055959.png

Additionally, the memory datasheet in section xSPI Signal Protocol Description specifies in octal DDR mode that the memory decodes the first byte of a valid command on the rising edge of the clock only during the command phase. Therefore, the repeated byte of the command extension becomes a dummy byte as shown by the screenshot below. This means that we can define the command OCTAL_IO_DTR_READ_CMD as FDFDh: the 8-bit MSB is decoded while the 8-bit LSB (repeated) is dummy. Some memory vendors have the pattern of command as cmd+inverted (case of Macronix)

 
AMEMCUSupportCenterBB_2-1766254097094.png

Update the command in main.h as follows:

#define OCTAL_IO_DTR_READ_CMD       0xFDFD

Follow the same process for the other commands: octal page programoctal read status registeroctal sector eraseoctal write enable, and write to the volatile configuration register based on Table 8.1 Command Set in the memory datasheet:

#define OCTAL_PAGE_PROG_CMD              0x1212
#define OCTAL_READ_STATUS_REG_CMD   0x0505
#define OCTAL_SECTOR_ERASE_CMD         0xD8D8
#define OCTAL_WRITE_ENABLE_CMD         0x0606
#define WRITE_VOL_REG_CMD                   0x81

The WRITE_VOL_REG_CMD command is defined as an 8-bit instruction since it is used before the Octal DDR mode is activated. Once Octal DDR mode is set, command instructions require to be 16-bit length. 

Dummy cycles are additional clock cycles inserted during read operations from serial NOR flash memory (for example, SPI NOR flash) to allow the memory device sufficient time to respond. The number of dummy cycles varies depending on whether the operation involves data access or register access.

For register read access, 8 clock cycles are required, as shown in the excerpt from the memory datasheet Table 8.1 Command Set.

 
AMEMCUSupportCenterBB_3-1766254147924.png

 For data read access, the default requirement is 16 clock cycles, as shown in the excerpt from the memory datasheet Table 8.1 Command Set.

 
AMEMCUSupportCenterBB_4-1766254183278.png

However, it is recommended to adjust the dummy cycles based on the XSPI clock frequency. In this example, the XSPI clock frequency is 100 MHz, so the appropriate dummy cycles configuration is 11. Table 6.7 of the memory datasheet shows the number of dummy clock cycles per operating clock frequency.

 
AMEMCUSupportCenterBB_5-1766254289216.png

The changes regarding the dummy cycles for both register and data read are as follows:

#define DUMMY_CLOCK_CYCLES_READ            11 // Has to match the dummy cycles setup in the configuration register
#define DUMMY_CLOCK_CYCLES_READ_OCTAL      8

The screenshot below illustrates all the changes in main.h. The left-hand side shows the modified code and the right-hand side shows the original code.

 
AMEMCUSupportCenterBB_6-1766254323798.png

Note that OCTAL_IO_READ_CMD and WRITE_CFG_REG_2_CMD are removed:

  • OCTAL_IO_READ_CMD is not utilized in the original example.
  • WRITE_CFG_REG_2_CMD is not applicable to ISSI memory as only one configuration register is available and the different features (for example, dummy cycles) are mapped to different addresses.

3.2 Editing main.c

The memory boots into SPI mode and single transfer rate (STR) so the command needs to be sent into SPI mode with instruction, address, data lines set to 1 line.

As discussed earlier, it is recommended to setup the dummy cycles configuration to 11 in this particular example where the XSPI clock frequency is 100MHz. For the IS25LXWX01G memory, the sequence is as follows:

  1. Send write enable (instruction = 0x06) to allow access to the volatile configuration register. The volatile configuration register is write-protected by default. The Write Enable command (0x06) must be issued first to set the Write Enable Latch (WEL) bit, allowing subsequent modification of the configuration register. The 0x06 instruction uses the 1-0-0 format with 0 dummy cycles, meaning instruction is required and no address and no data are required, as shown in the code snippet below. 
  2. Write 11 to the volatile configuration register (instruction = 0x81) at address 0x00000001 as shown by the code below. The Volatile Configuration Register Write command (0x81) is used to update the register at address 0x01, which controls the number of dummy cycles for read operations. The 0x81 instruction uses the 1-1-1 format with 0 dummy cycles meaning instruction a 3-byte address (default 24 bits), and 1 byte of data. Therefore, the address width is set to 24 bits and the data length to 1, as shown in the code below. 

The table below summarizes the two steps:

Command Instruction Format Dummy cycles Address Data Explanation
Write Enable 0x06 1-0-0 0 None None Enable writing to configuration register
Write register 0x81 1-1-1 0 0x01 1 byte = 11 (decimal) Set dummy cycles

The code implementation for this sequence is shown below: 

  // Setup dummy cycles
  /* Step 1: Write Enable */
  sCommand.OperationType      = HAL_XSPI_OPTYPE_COMMON_CFG;
  sCommand.InstructionMode    = HAL_XSPI_INSTRUCTION_1_LINE;
  sCommand.Instruction        = WRITE_ENABLE_CMD;
  sCommand.AddressMode        = HAL_XSPI_ADDRESS_NONE;
  sCommand.DataMode           = HAL_XSPI_DATA_NONE;
  sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
  sCommand.DQSMode            = HAL_XSPI_DQS_DISABLE;
  
  if(HAL_XSPI_Command(hxspi, &sCommand, 0xfff) != HAL_OK)
  {
    Error_Handler();
  }
  
  /* Step 2: write the volatile configuration  */
  sCommand.OperationType      = HAL_XSPI_OPTYPE_COMMON_CFG;
  sCommand.Instruction        = WRITE_VOL_REG_CMD;
  sCommand.InstructionMode    = HAL_XSPI_INSTRUCTION_1_LINE;
  sCommand.InstructionWidth   = HAL_XSPI_INSTRUCTION_8_BITS;
  sCommand.AddressMode        = HAL_XSPI_ADDRESS_1_LINE;
  sCommand.AddressWidth       = HAL_XSPI_ADDRESS_24_BITS;
  sCommand.Address            = 0x00000001; // dummy cycles configuration
  sCommand.DummyCycles        = 0;
  sCommand.DataMode           = HAL_XSPI_DATA_1_LINE;
  sCommand.DataLength         = 1;
  sCommand.DataDTRMode        = HAL_XSPI_DATA_DTR_DISABLE;
  
  reg = DUMMY_CLOCK_CYCLES_READ; // 11 clock cycles at 105MHz
  if(HAL_XSPI_Command(hxspi, &sCommand, 0xfff) != HAL_OK)
  {
    Error_Handler();
  }
  if(HAL_XSPI_Transmit(hxspi, (uint8_t*)&reg, 0xffff) != HAL_OK)
  {
    Error_Handler();
  }

If your application changes the XSPI clock frequency, you may need to adjust the dummy cycles accordingly to maintain data integrity from the external serial flash.

The default memory address range is limited to 24 bits, which means only the first 128 Mb are accessible. To access the full memory content (1 Gb with IS25LXWX01G), it is necessary to enable the 4-byte address mode. To achieve this, perform the following steps:

  1. Send a write enable command (instruction: 0x06) to allow access to the volatile configuration register.
  2. Write 0xFE to the volatile configuration register (instruction: 0x81) at address 0x05, as illustrated below.

This step is not mandatory. Once the device is configured to Octal DDR mode in the next step, it will be fixed to 4-byte address mode. This is regardless of the register configuration at address 0x05, whether set to 0xff or 0xfe.

 
AMEMCUSupportCenterBB_7-1766254381474.png

The code implementation for this sequence is shown below:

  // Enable 4Byte addressing  
  /* Step 1: Write Enable */
  sCommand.OperationType      = HAL_XSPI_OPTYPE_COMMON_CFG;
  sCommand.InstructionMode    = HAL_XSPI_INSTRUCTION_1_LINE;
  sCommand.InstructionWidth    = HAL_XSPI_INSTRUCTION_8_BITS;
  sCommand.Instruction        = OCTAL_WRITE_ENABLE_CMD; /* Write volatile configuration register */
  sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
  sCommand.AddressMode        = HAL_XSPI_ADDRESS_NONE;
  sCommand.DataMode           = HAL_XSPI_DATA_NONE;
  sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
  sCommand.DQSMode            = HAL_XSPI_DQS_ENABLE;
  
  if(HAL_XSPI_Command(hxspi, &sCommand, 0xfff) != HAL_OK)
  {
    Error_Handler();
  }
  
  /* Step 2: write the volatile configuration  */
  sCommand.OperationType      = HAL_XSPI_OPTYPE_COMMON_CFG;
  sCommand.Instruction        = WRITE_VOL_REG_CMD; /* Write volatile configuration register */
  sCommand.InstructionMode    = HAL_XSPI_INSTRUCTION_1_LINE;
  sCommand.InstructionWidth    = HAL_XSPI_INSTRUCTION_8_BITS;
  sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
  sCommand.AddressMode        = HAL_XSPI_ADDRESS_1_LINE;
  sCommand.AddressWidth        = HAL_XSPI_ADDRESS_24_BITS;
  sCommand.Address            = 0x00000005; // 4Byte configuration at offset 05h
  sCommand.AddressDTRMode     = HAL_XSPI_ADDRESS_DTR_DISABLE;
  sCommand.DummyCycles        = 0;
  sCommand.DataMode           = HAL_XSPI_DATA_1_LINE;
  sCommand.DataLength         = 1;
  sCommand.DataDTRMode        = HAL_XSPI_DATA_DTR_DISABLE;
  
  reg = 0xFE; // Beyond 128Mb address configuration FEh = 4-byte address - FFh = 3-byte address (Default)
  if(HAL_XSPI_Command(hxspi, &sCommand, 0xfff) != HAL_OK)
  {
    Error_Handler();
  }
  if(HAL_XSPI_Transmit(hxspi, (uint8_t*)&reg, 0xffff) != HAL_OK)
  {
    Error_Handler();
  }

To enable the Octal Double Transfer Rate (DTR) mode, a specific command must be sent to the memory using the XSPI_NOR_OctalDTRModeCfg() function. 

For the IS25LXWX01G memory, the sequence is as follows:

  1. Send write enable (instruction = 0x06) allowing access to the volatile configuration register. The 0x06 instruction uses the 1-0-0 format (one I/O line for the command, no address, or data) and 0 dummy cycles.
  2. Write 0xE7 to the volatile configuration register (instruction = 0x81) at address 0x00, as shown below.  The 0x81 instruction uses the 1-1-1 format (one I/O line for command, address, and data), 0 dummy cycles, a 4-byte address (default 24 bits and changed in previous step to 32 bits, here set to 0x00), and 1 byte of data (0xE7).
 
AMEMCUSupportCenterBB_8-1766254418826.png

The code implementation for this sequence is shown below. Note that some extra polling steps are present in the code. These are not strictly required for enabling Octal DTR mode, but have been included to maintain consistency with the original code and minimize modifications.

  // Setup Octal DDR mode
  sCommand.OperationType      = HAL_XSPI_OPTYPE_COMMON_CFG;
  sCommand.InstructionMode    = HAL_XSPI_INSTRUCTION_1_LINE;
  sCommand.InstructionWidth    = HAL_XSPI_INSTRUCTION_8_BITS;
  sCommand.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
  sCommand.AddressDTRMode     = HAL_XSPI_ADDRESS_DTR_DISABLE;
  sCommand.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
  sCommand.DataDTRMode        = HAL_XSPI_DATA_DTR_DISABLE;
  sCommand.DummyCycles        = 0;
  sCommand.DQSMode            = HAL_XSPI_DQS_DISABLE;
  sConfig.MatchMode           = HAL_XSPI_MATCH_MODE_AND;
  sConfig.AutomaticStop       = HAL_XSPI_AUTOMATIC_STOP_ENABLE;
  sConfig.IntervalTime        = 0x10;


  /* Enable write operations */
  sCommand.Instruction = WRITE_ENABLE_CMD;
  sCommand.DataMode    = HAL_XSPI_DATA_NONE;
  sCommand.AddressMode = HAL_XSPI_ADDRESS_NONE;

  if (HAL_XSPI_Command(hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

  /* Reconfigure XSPI to automatic polling mode to wait for write enabling */
  sConfig.MatchMask           = 0x02;
  sConfig.MatchValue          = 0x02;

  sCommand.Instruction        = READ_STATUS_REG_CMD;
  sCommand.DataMode           = HAL_XSPI_DATA_1_LINE;
  sCommand.DataLength         = 1;

  if (HAL_XSPI_Command(hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_XSPI_AutoPolling(hxspi, &sConfig, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

  /* Write Configuration register 2 (with Octal I/O SPI protocol) */
  sCommand.Instruction  = WRITE_VOL_REG_CMD;
  sCommand.AddressMode  = HAL_XSPI_ADDRESS_1_LINE;
  sCommand.AddressWidth = HAL_XSPI_ADDRESS_32_BITS;

  sCommand.Address = 0;
  reg = 0xe7; // E7h = Octal DDR


  if (HAL_XSPI_Command(hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_XSPI_Transmit(hxspi, &reg, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

  sCommand.Instruction    = READ_STATUS_REG_CMD;
  sCommand.DataMode       = HAL_XSPI_DATA_1_LINE;
  sCommand.DataLength     = 1;

  if (HAL_XSPI_Command(hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

  if (HAL_XSPI_AutoPolling(hxspi, &sConfig, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    Error_Handler();
  }

At this stage, all instructions and addresses must be coded in octal DDR with DQS enabled. The instruction, address, and data modes when available is set to 8 lines.

The complete modified source code is available here: stm32-hotspot/STM32N6_XSPI_IS25LXWX01G.  

Conclusion

In this article, we walked through the key steps to successfully port the STM32N6570 XSPI memory-mapped example from the MX66UW1G45G to the IS25LXWX01G memory. By adjusting the XSPI settings, updating command codes, and fine-tuning dummy cycles, you can ensure successful communication with the new memory chip. With these changes, your STM32N6 XSPI memory-mapped DTR example works with the IS25LXWX01G.

Version history
Last update:
‎2026-01-27 6:38 AM
Updated by: