STM32H5を使ってみる4 -STM32L5の時みたいにOCTO-SPIを使ってみる-

20240516追:
秋月さんよりNUCLEO-H563ZI販売開始です!!
20240516追:



つい最近、STM32H7シリーズにSTM32H7R/Sというかなり尖った品種
追加されましたが思い出したようにSTM32H5を触っていこうと思います。

20240321追:
STM32U0シリーズなんてのも出現してました…!!
20240321追:


今回もSTM32H563ZI-NUCLEOを使用したFatFsの移植デモのソースコード
使用して解説していきます。


●STM32L5から進化したSTM32H5のOCTO-SPI
STM32H5のOCTO-SPIはポピュラーなQUADSPI-ROMだけではなく、最大
16bitなクロックドシリアルROM/PSRAMなんかも接続可能で、もちろん
メモリマップドなアクセスが可能となっております。
シリアルなのに16bitもパラレルってなんなんだよと常に感じてますが
時代は常に進化していっているなと同じく感じております(混乱)。
sirius506さんはそんなOCTO-SPIを完全に使いこなしDOOMというゲームを
移植されており
、ほんとに感心してしまいました…

ねむいさんは組み込み系界隈の中では雑魚キャラに過ぎないのでそんな
高度なことはできず、基本中の基本のQSPI-ROMを接続してメモリ
マップドモードで読み出し専用で動かすことからやってきます。

OCTO-SPIについてはST公式のAN5050もしっかり熟読してください。



●STM32H5のOCTO-SPIをメモリマップドモードで動かすコード
移植に当たってはSTM32L5のやつをそのままスライド移植…というわけには
簡単にいかず、L5のOCTO-SPIのコードから少し改変が必要でした。



その前に、STM32H563ZI-NUCLEOでOCTO-SPIを使う際はSB70をジャンパして
PE2(COTO-SPI IO2)を外部に引き出せられるようにするのを忘れないで
くださいね。

それではSTM32H5のOCTO-SPIのメモリマップドモードで使うためのコードを
紹介します。まずはI/O

/**************************************************************************/
/*! 
    @brief	OCTO-SPI GPIO Configuration.
	@param	None.
    @retval	None.
*/
/**************************************************************************/
void OSPI_IoInit_If(void)
{
	GPIO_InitTypeDef GPIO_InitStruct = {0};
	RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};

	/* Initializes the peripherals clock */
	PeriphClkInit.PeriphClockSelection 	= RCC_PERIPHCLK_OSPI;
	PeriphClkInit.OspiClockSelection 	= RCC_OSPICLKSOURCE_HCLK;
	if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
	{
		for(;;);
	}
	
	/* Peripheral clock enable */
	__HAL_RCC_OSPI1_CLK_ENABLE();
	
	/* Alternate GPIO enable */
	__HAL_RCC_GPIOB_CLK_ENABLE();
	__HAL_RCC_GPIOD_CLK_ENABLE();
	__HAL_RCC_GPIOE_CLK_ENABLE();
	__HAL_RCC_GPIOG_CLK_ENABLE();

	/* PD11 as OSPI_IO0 */
	GPIO_InitStruct.Pin 		= GPIO_PIN_11;
	GPIO_InitStruct.Mode 		= GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pull 		= GPIO_NOPULL;
	GPIO_InitStruct.Speed 		= GPIO_SPEED_FREQ_VERY_HIGH;
	GPIO_InitStruct.Alternate   = GPIO_AF9_OCTOSPI1;
	HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

	/* PD12 as OSPI_IO1 */
	GPIO_InitStruct.Pin 		= GPIO_PIN_12;
	GPIO_InitStruct.Alternate   = GPIO_AF9_OCTOSPI1;
	HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);

	/* PE2 as OSPI_IO2(Need SB70 SolderBridge) */
	GPIO_InitStruct.Pin 		= GPIO_PIN_2;
	GPIO_InitStruct.Alternate   = GPIO_AF9_OCTOSPI1;
	HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

	/* PD13 as OSPI_IO3 */
	GPIO_InitStruct.Pin 		= GPIO_PIN_13;
	GPIO_InitStruct.Alternate   = GPIO_AF9_OCTOSPI1;
	HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
	
	/* PB2 as OSPI_CLK */
	GPIO_InitStruct.Pin		 	= GPIO_PIN_2;
	GPIO_InitStruct.Alternate   = GPIO_AF9_OCTOSPI1;
	HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
	
	/* PG6 as OSPI_NCS */
	GPIO_InitStruct.Pin 		= GPIO_PIN_6;
	GPIO_InitStruct.Alternate   = GPIO_AF10_OCTOSPI1;
	HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
}



お次はOCTO-SPIのメモリマップドモードの初期化です。
/**************************************************************************/
/*! 
    @brief	Configure OCTO-SPI as Memory Mapped Mode.
			Winbond W25Q128JVSIQ specific setting.
			* Address size is 24bit(16MBytes).
			* Initially sets QUAD-MODE(not need quadmode command).
			* MAX 133MHz CLK.
			* Support "XIP",thus suitable for MemoryMappedMode.
	@param	None.
    @retval	None.
*/
/**************************************************************************/
void Set_OSPI_MemoryMappedMode(void)
{
	XSPI_RegularCmdTypeDef sCommand = {0};
	XSPI_MemoryMappedTypeDef sMemMappedCfg = {0};
	uint8_t reg_data =0;
	
	/* Initialize OCTO-SPI I/O */
	OSPI_IoInit_If();
	
	/* Initialize OCTO-SPI */
	hxspi.Instance 						= OCTOSPI1;
	hxspi.Init.FifoThresholdByte 		= 1;
	hxspi.Init.MemoryMode 				= HAL_XSPI_SINGLE_MEM;
	hxspi.Init.MemoryType 				= HAL_XSPI_MEMTYPE_MICRON;
	hxspi.Init.MemorySize 				= 23;	/* 128Mbit=16MByte=2^(23+1) W25Q128JVSIQ */
	hxspi.Init.ChipSelectHighTimeCycle 	= 4;	/* 4ClockCycle(16nSec@250MHzMAX) Need for W25Q128JVSIQ >10nSec@read */
	hxspi.Init.FreeRunningClock 		= HAL_XSPI_FREERUNCLK_DISABLE;
	hxspi.Init.ClockMode 				= HAL_XSPI_CLOCK_MODE_0;
	hxspi.Init.WrapSize 				= HAL_XSPI_WRAP_NOT_SUPPORTED;
	hxspi.Init.ClockPrescaler 			= 1;	/* 250MHzMAX/(1+1) = 125MHz (Allowed SDR Clock is 150MHz@3.3V) */
	hxspi.Init.SampleShifting 			= HAL_XSPI_SAMPLE_SHIFT_HALFCYCLE;
	hxspi.Init.DelayHoldQuarterCycle 	= HAL_XSPI_DHQC_DISABLE;
	hxspi.Init.ChipSelectBoundary 		= 0;
	hxspi.Init.DelayBlockBypass 		= HAL_XSPI_DELAY_BLOCK_BYPASS;
	hxspi.Init.Refresh 					= 0;
	if (HAL_XSPI_Init(&hxspi) != HAL_OK)
	{
		for(;;);
	}
	
	/* Enable Reset --------------------------- */
	/* Common Commands */
	sCommand.OperationType      		= HAL_XSPI_OPTYPE_COMMON_CFG;
	sCommand.IOSelect           		= HAL_XSPI_SELECT_IO_3_0;
	sCommand.InstructionDTRMode 		= HAL_XSPI_INSTRUCTION_DTR_DISABLE;
	sCommand.AddressDTRMode     		= HAL_XSPI_ADDRESS_DTR_DISABLE;
	sCommand.DataDTRMode				= HAL_XSPI_DATA_DTR_DISABLE;
	sCommand.DQSMode            		= HAL_XSPI_DQS_DISABLE;
	sCommand.SIOOMode          			= HAL_XSPI_SIOO_INST_EVERY_CMD;
	sCommand.AlternateBytesMode 		= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytes				= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytesWidth		= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytesDTRMode		= HAL_XSPI_ALT_BYTES_DTR_DISABLE;
	sCommand.InstructionMode   			= HAL_XSPI_INSTRUCTION_1_LINE;
	sCommand.InstructionWidth    		= HAL_XSPI_INSTRUCTION_8_BITS;
	sCommand.AddressWidth 				= HAL_XSPI_ADDRESS_24_BITS;
	/* Instruction */
	sCommand.Instruction 				= 0x66;	/* Reset Enable W25Q128JVSIQ */
	/* Address */
	sCommand.AddressMode       			= HAL_XSPI_ADDRESS_NONE;
	sCommand.Address					= 0;						
	/* Data */
	sCommand.DataMode          			= HAL_XSPI_DATA_NONE;
	sCommand.DataLength       			= 0;
	sCommand.DummyCycles       			= 0;				
	
	if (HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		for(;;);
	}
	
	/* Reset Device --------------------------- */
	/* Common Commands */
	sCommand.OperationType      		= HAL_XSPI_OPTYPE_COMMON_CFG;
	sCommand.IOSelect           		= HAL_XSPI_SELECT_IO_3_0;
	sCommand.InstructionDTRMode 		= HAL_XSPI_INSTRUCTION_DTR_DISABLE;
	sCommand.AddressDTRMode     		= HAL_XSPI_ADDRESS_DTR_DISABLE;
	sCommand.DataDTRMode				= HAL_XSPI_DATA_DTR_DISABLE;
	sCommand.DQSMode            		= HAL_XSPI_DQS_DISABLE;
	sCommand.SIOOMode          			= HAL_XSPI_SIOO_INST_EVERY_CMD;
	sCommand.AlternateBytesMode 		= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytes				= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytesWidth		= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytesDTRMode		= HAL_XSPI_ALT_BYTES_DTR_DISABLE;
	sCommand.InstructionMode   			= HAL_XSPI_INSTRUCTION_1_LINE;
	sCommand.InstructionWidth    		= HAL_XSPI_INSTRUCTION_8_BITS;
	sCommand.AddressWidth 				= HAL_XSPI_ADDRESS_24_BITS;
	/* Instruction */
	sCommand.Instruction 				= 0x99;	/* Reset W25Q128JVSIQ */
	/* Address */
	sCommand.AddressMode       			= HAL_XSPI_ADDRESS_NONE;
	sCommand.Address					= 0;
	/* Data */
	sCommand.DataMode          			= HAL_XSPI_DATA_NONE;
	sCommand.DataLength       			= 0;
	sCommand.DummyCycles       			= 0;
	
	if (HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		for(;;);
	}

	/* Enter Quad-SPI Mode --------------------------- */
	/* Common Commands */
	sCommand.OperationType      		= HAL_XSPI_OPTYPE_COMMON_CFG;
	sCommand.IOSelect           		= HAL_XSPI_SELECT_IO_3_0;
	sCommand.InstructionDTRMode 		= HAL_XSPI_INSTRUCTION_DTR_DISABLE;
	sCommand.AddressDTRMode     		= HAL_XSPI_ADDRESS_DTR_DISABLE;
	sCommand.DataDTRMode				= HAL_XSPI_DATA_DTR_DISABLE;
	sCommand.DQSMode            		= HAL_XSPI_DQS_DISABLE;
	sCommand.SIOOMode          			= HAL_XSPI_SIOO_INST_EVERY_CMD;
	sCommand.AlternateBytesMode 		= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytes				= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytesWidth		= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytesDTRMode		= HAL_XSPI_ALT_BYTES_DTR_DISABLE;
	sCommand.InstructionMode   			= HAL_XSPI_INSTRUCTION_1_LINE;
	sCommand.InstructionWidth    		= HAL_XSPI_INSTRUCTION_8_BITS;
	sCommand.AddressWidth 				= HAL_XSPI_ADDRESS_24_BITS;
	/* Instruction */
	sCommand.Instruction 				= 0x31;	/* Set Status2 W25Q128JVSIQ */
	/* Address */
	sCommand.AddressMode       			= HAL_XSPI_ADDRESS_NONE;
	sCommand.Address					= 0;
	/* Data */
	sCommand.DataMode          			= HAL_XSPI_INSTRUCTION_1_LINE;
	sCommand.DataLength       			= 1;
	sCommand.DummyCycles       			= 0;
	reg_data 							= 0x02;	/* Enable QuadI/O Mode */
	
	if (HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		for(;;);
	}
	
	if (HAL_XSPI_Transmit(&hxspi, ®_data, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		for(;;);
	}

	/* Enter MemoryMappedMode --------------------------- */
	/* Read Commands */
	sCommand.OperationType      		= HAL_XSPI_OPTYPE_READ_CFG;
	sCommand.IOSelect           		= HAL_XSPI_SELECT_IO_3_0;
	sCommand.InstructionDTRMode 		= HAL_XSPI_INSTRUCTION_DTR_DISABLE;
	sCommand.AddressDTRMode     		= HAL_XSPI_ADDRESS_DTR_DISABLE;
	sCommand.DataDTRMode				= HAL_XSPI_DATA_DTR_DISABLE;
	sCommand.DQSMode            		= HAL_XSPI_DQS_DISABLE;
	sCommand.SIOOMode          			= HAL_XSPI_SIOO_INST_EVERY_CMD;
	sCommand.AlternateBytesMode 		= HAL_XSPI_ALT_BYTES_4_LINES;
	sCommand.AlternateBytes				= 0xFF;	/* Need for Fast Read QUAD W25Q128JVSIQ */
	sCommand.AlternateBytesWidth		= HAL_XSPI_ALT_BYTES_8_BITS;
	sCommand.AlternateBytesDTRMode		= HAL_XSPI_ALT_BYTES_DTR_DISABLE;
	sCommand.InstructionMode   			= HAL_XSPI_INSTRUCTION_1_LINE;
	sCommand.InstructionWidth    		= HAL_XSPI_INSTRUCTION_8_BITS;
	sCommand.AddressWidth				= HAL_XSPI_ADDRESS_24_BITS;
	/* Instruction */
	sCommand.Instruction 				= 0xEB;	/* Fast Read QUAD W25Q128JVSIQ */
	/* Address */
	sCommand.AddressMode       			= HAL_XSPI_ADDRESS_4_LINES;
	sCommand.Address					= 0;								
	/* Data */	
	sCommand.DataMode          			= HAL_XSPI_DATA_4_LINES;
	sCommand.DataLength       			= 0;
	sCommand.DummyCycles       			= 4;	/* DUMMY 4Cycle for Fast Read QUAD W25Q128JVSIQ */

	if(HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		for(;;);
	}
	
	/* Write Commands */
	sCommand.OperationType      		= HAL_XSPI_OPTYPE_WRITE_CFG;
	sCommand.IOSelect           		= HAL_XSPI_SELECT_IO_3_0;
	sCommand.InstructionDTRMode 		= HAL_XSPI_INSTRUCTION_DTR_DISABLE;
	sCommand.AddressDTRMode     		= HAL_XSPI_ADDRESS_DTR_DISABLE;
	sCommand.DataDTRMode				= HAL_XSPI_DATA_DTR_DISABLE;
	sCommand.DQSMode            		= HAL_XSPI_DQS_DISABLE;
	sCommand.SIOOMode          			= HAL_XSPI_SIOO_INST_EVERY_CMD;
	sCommand.AlternateBytesMode 		= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytes				= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytesWidth		= HAL_XSPI_ALT_BYTES_NONE;
	sCommand.AlternateBytesDTRMode		= HAL_XSPI_ALT_BYTES_DTR_DISABLE;
	sCommand.InstructionMode   			= HAL_XSPI_INSTRUCTION_1_LINE;
	sCommand.InstructionWidth    		= HAL_XSPI_INSTRUCTION_8_BITS;
	sCommand.AddressWidth				= HAL_XSPI_ADDRESS_24_BITS;
	/* Instruction */
	sCommand.Instruction 				= 0x32;	/* Page Write QUAD W25Q128JVSIQ */
	/* Address */
	sCommand.AddressMode       			= HAL_XSPI_ADDRESS_1_LINE;
	sCommand.Address					= 0;		
	/* Data */
	sCommand.DataMode          			= HAL_XSPI_DATA_4_LINES;
	sCommand.DataLength       			= 0;
	sCommand.DummyCycles       			= 0;							
	
	if(HAL_XSPI_Command(&hxspi, &sCommand, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		for(;;);
	}

	/* Set OCTO-SPI as MemoryMappedMode */
	sMemMappedCfg.TimeOutActivation 	= HAL_XSPI_TIMEOUT_COUNTER_DISABLE;
	sMemMappedCfg.TimeoutPeriodClock 	= 0;
	if(HAL_XSPI_MemoryMapped(&hxspi, &sMemMappedCfg) != HAL_OK)
	{
        for(;;);
    }

}


STM32H5のOCTO-SPIのHALはL5と微妙に違っており"HAL_OSPI"だったのが
"HAL_XSPI"に変わってたりして移植の際めっちゃ鬱陶しかったのですが
基本的な設定の流れはL5の時とそこまで変わっておりません。

特筆すべきはSTM32H5は250MHzで動作するのでW25Q128JVSIQ(最大133MHz)
に対して125MHzという過激なスピードのクロックが供給可能で非常に高速な
動作が可能です!しかもデータキャッシュもついてます!

ちなみにSTM32H5のOCTO-SPIの最大供給クロックは150MHz(3.3V時)まで
可能となっております。

●実際に動かしてみる
ていうかポリウレタン被覆銅線でQSPI-ROMへ配線引き伸ばしてほんとに
125MHzで動いてンのかと自分でも訝しんでいましたので実際にクロックの
波形を測定してみました。
使用したオシロはPicoscope5444D MSOですそうだね宣伝だね。


Picoscope5444D MSOのアナログ帯域200MHzなのに125MHzの高速信号測るの
どうなのとお思いでしょうが測定限界を大幅に超えていますがETSモードで
測定した限りではきっちり125MHzのクロックが出ていることがわかります。


ねむいさんはQSPI-ROMをFONTX2を格納するのに使っていますがこんな感じで
文字崩れも起こさずきれいに表示できております!

手配線の限界に挑んだ感じですがまぁSCLKは一応33ohmで直列終端
しているのでそれが効いて安定して動作しているのでしょう~。



●おまけ・STM32L5とのパフォーマンス比較
せっかく下駄基板作ってハード的に同じ条件が揃えてるのでSTM32L5と
STM32H5のパフォーマンス比較とかやってみました。
同一のpngファイルでlibpngを使ったデコード時間を比較してみました。

☝STM32L5の場合(動作クロック:110MHz)

☝STM32H5の場合(動作クロック:250MHz)
う~むやはり圧倒的に違いますね~というかねむいさんがSTM32H5の
パワーを持て余しててまだまだ機能の一握りも使えてないので頑張って
使いこなしていこうと思います!これからも頑張るぞい!

Go to top of page