STM32H7を使ってみる5 -キャッシュ・ワンダリング(後篇)-
←前回                       次回→
このぶろぐの読者様的にはねむいさんがうっかり「MDMAを使ってみる」などという
公序良俗(こうじょりょうぞく)に大幅に反したタイトルの記事を書いてしまって
警察屋さんに絞られる展開を期待してるのでしょうがそんな安易な手には引っかかり
ませんよぅ!!!てうかそんな明らかにヤバイ合成(まぜ)モノより天然自然のta
ダメ!ゼッタイ!!!
さて今回はエラッタのせいで強制的にライトバックにせざるを得なくなってしまった
キャッシュメモリでDMAとどうやってうまいこと付き合っていくかを解説していこうと
思います。
なお、DMA使うメモリ領域だけキャッシュ不可にするという軟弱な措置はねむいさんの
ライブラリに存在しませんのであしからず!!!!!!!!
●注意すべき前提
キャッシュがきいた状態のメモリアクセスでは、あるひとまとまりのデータの塊
に加えてそのデータ塊のアドレス境界で処理すべきという大大前提があります。
これはキャッシュを搭載するどのCPUでも同一となります。
「あるひとまとまり」はキャッシュメモリ上のキャッシュラインのバイト数を示し、
「アドレス境界」はアドレスがキャッシュラインのバイト数分のビットで同一である
ということを示します。
Cortex-M7においてはデータキャッシュのキャッシュラインサイズが32Byteのため、
「32バイトごと」かつ「アドレスの32バイトに当たる下位5ビットが0x0_0000」
であることが求められます。
過去にSTM32F7でキャッシュ比較したときはあらかじめDMA先を32バイトの境界に
合わせ4kByte刻みで転送という限定的な環境でやってたのでキャッシュコントロールは
かなり端折った記述をしておりましたが今回は腰を据えて汎用的に扱えるよう
落とし込んて行きます。
●DMAで読み取る場合(SDMMCのIDMAの場合)
実はライトバック設定の場合、READの取り扱いが一番やっかいです。
理論的には読み出し先のメモリのキャッシュをInvalidateしてやればよいはずなのですが
なぜかそうは問屋が卸さず、私が試した限りでは必ずCleanもしなけらばならないことが
分かりました?なんでだYO!
DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
	DRESULT res = RES_OK;
    uint32_t timer = SysTick->VAL + SD_DATATIMEOUT;
    /* first ensure the SDCard is ready for a new operation */
    while((SD_GetCardState() == SD_TRANSFER_BUSY))
    {
		if(timer < SysTick->VAL)
		return RES_NOTRDY;
    }
#if defined(SD_DMA_MODE) && !defined(SD_POLLING_MODE)
    /*
       Force Cache Data Lines content write back to memory and Invalidate them.
       Someone (CPU) might be still accessing those cache lines so we need to
       flush/clean recent content to memory and purge/invalid cache lines before
       allowing Hardware to access RAM.
     */
	if((uintptr_t)buff & 0x3)	/* Check 4Byte Alignment */
	{	/* Unaligned Buffer Address Case (Slower) */
		for (unsigned int secNum = 0; secNum < count ; secNum++){
			SCB_InvalidateDCache_by_Addr ((uint32_t*)dmabuf, SECTOR_SIZE);
			if(SD_ReadBlocks_DMA((uint32_t*)dmabuf, (uint32_t)(sector+secNum), 1)!= MSD_OK)
			{
				MSG_PRINTF("Read error on unaligned buffer¥n");
				res = RES_ERROR;
			}
			memcpy(buff+secNum*SECTOR_SIZE, dmabuf, SECTOR_SIZE);
		}
	} else {
		/* Aligned Buffer Address Case (Faster) */
		SCB_CleanInvalidateDCache_by_Addr((uint32_t*)buff, count * SECTOR_SIZE);
		if(SD_ReadBlocks_DMA((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
		{
			MSG_PRINTF("Read error on DMA¥n");
			res = RES_ERROR;
		}
	}
#else
	if(SD_ReadBlocks((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
	{
		MSG_PRINTF("Read error on polling¥n");
		res = RES_ERROR;
	}
#endif
	return res;
}↑CMSIS_v5を使ってSTM32H7でDMA転送(READ)する場合です。
READアクションをとる前にSCB_CleanInvalidateDCache_by_Addr()を実行します。
Invalidateだけではだめです。CleanしてInvalidateです。ここテストに出るので
覚えておきまししょう!そうせざるを得なかった例がこちらの議論にもありますが正直
私もピンときません。だれか詳しい解説を教えてください。
当たり前ですがデータが32Byteの倍数かつ32Byte境界にビッチリそろった完全読み出し
専用のメモリ領域ならInvalidateだけで問題ないです。ぇ?自分で答え言ってるだろ
って?ねむいさんは他の人の同意とか同情がほしいのですよぅ!
ぁあそうだ、ライトスルーもInvalidateだけでおk、エラッタでどうせ使えませんが。
それと上記のルーチンでは先の述べたアドレスの丸め込みがないじゃないかと思う
方がいると思いますがCMSIS_v5ライブラリのほうに丸め込み処理が取り込まれており、
ゴテゴテしたマクロ組まなくてもシンプルに記述できるようになっています。
STマイクロのCubeライブラリではこちらの更新がまだ取り入れられておらず、自前の
アドレス丸め込みマクロが記述されておりますので二度手間にならないようにご注意
願います。
そんでもってCleanInvalidateしたことによるキャッシュコントロールによる
オーバーヘッドが懸念されますがH7はCPUのコアの速度がMAX480MHzも出るので
多分気にはならないとおもいます(投げやり)
●DMAで送信する場合(SDMMCのIDMAの場合)
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
	DRESULT res = RES_ERROR;
    uint32_t timer;
#if defined(SD_DMA_MODE) && !defined(SD_POLLING_MODE)
    /*
       Force Cache Data Lines content write back to memory.
       Someone (CPU) might be still accessing those cache lines so we need to
       flush/clean recent content to memory before
       allowing Hardware to access RAM.
     */
	if((uintptr_t)buff & 0x3)	/* Check 4Byte Alignment */
	{	/* Unaligned Buffer Address Case (Slower) */
		for (unsigned int secNum = 0; secNum < count; secNum++){
			memcpy(dmabuf, buff+(SECTOR_SIZE*secNum), SECTOR_SIZE);
			SCB_CleanDCache_by_Addr((uint32_t*)dmabuf, SECTOR_SIZE);
			if(SD_WriteBlocks_DMA((uint32_t*)dmabuf, (uint32_t)(sector+secNum), 1) != MSD_OK)
			{
				MSG_PRINTF("Write error on unaligned buffer¥n");
				res = RES_ERROR;
			}
		}
	} else {
		SCB_CleanDCache_by_Addr((uint32_t*)buff, count * SECTOR_SIZE);
		if(SD_WriteBlocks_DMA((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
		{
			MSG_PRINTF("Write error on DMA¥n");
			res = RES_ERROR;
		}
	}
#else
	if(SD_WriteBlocks((uint32_t*)buff, (uint32_t)sector, count) != MSD_OK)
	{
		MSG_PRINTF("Write error on polling¥n");
		res = RES_ERROR;
	}
#endif
	/* ensure the SDCard is ready for a next operation */
	timer = SysTick->VAL + SD_DATATIMEOUT;
	res = RES_ERROR; /* Timeout */
	/* block until SDIO IP is ready or a timeout occur */
	while(timer > SysTick->VAL)
	{
		if(SD_GetCardState() == SD_TRANSFER_OK)
		{
			res = RES_OK;
			break;
		}
	}
	return res;
}↑CMSIS_v5を使ってSTM32H7でDMA転送(WRITE)する場合です。
WRITEのほうは単純明快、転送前にCleanしてやるだけです!
READのほうが扱いがめどくて誤れば致命的なのがなんとも…
ちなみにSTM32のマイコンにおいてREAD/WRITEいずれの場合も
DMAコントローラのWORD(4Byte)制限があるのでそれも加味しておきましょう。
●DMAで送信する場合(SAIのCircularDMAの場合)
ライトスル―が使えなくなったせいでいろんなコードにキャッシュコントロールを
突っ込まざるを得なくなってしまいましたがねむいさんのいつものではAudio再生に
SAIのCircularDMAを使用しており、こちらもきっちり対策しております。
ねむいさん最初にH7触った時に無対策で挑んで見事返り討ちに会いました。サンプル
音源のBeachBoysのハーモニック・サウンドがブビビビッブッというげりうんちみたいな
汚い音が得られてしまったのですが移植時にcodecの音量設定が最大になっていたのも
気づかずげりうんち音で鼓膜が破壊されるかと思いましたがどうでもいいですね。
SAIの場合はWrite専用のぶっぱなのでCleanだけで問題ないです。
問題は挿入する箇所です。
/* on the first buffer fill up,start the dma transfer */
	if(EVAL_AUDIO_Init(OUTPUT_DEVICE_BOTH, DEFAULT_VOLUME, (uint32_t)mp3FrameInfo.samprate))
	{
		MSG_PRINTF("Mp3Decode: audio init failed¥r¥n");
		nResult = -4;
		break;
	}
	/* I2S DMA Transfer First Trigger */
#if defined(STM32H7XX)
	SCB_CleanDCache_by_Addr((uint32_t*)g_pMp3DmaBuffer, MP3_DMA_BUFFER_SIZE);
#endif
	/* Needed to AAC_DMA_BUFFER_SIZE*2,16-bits audio data size(2Byte) */
	/* See more detail,BSP_AUDIO_OUT_Play()							  */	
	EVAL_AUDIO_Play(g_pMp3DmaBuffer, MP3_DMA_BUFFER_SIZE*2);↑転送なのでCleanのみでおk(mp3.support.cにて)
まずはCircularDMAの最初のキックです。まぁこれは当然。
ていうかなんかtypoしてますね私orz AACじゃなくてここではMP3です。
if(unDmaBufMode == INIT_RINGBUF || unDmaBufMode == FULL_RINGBUF)
{
	/* Check FirstHalf Transfer in LastHalf and BufferInit Mode */
	if(em & TRANSFER_FIRST_HALF)
	{
		MSG_PRINTF("Mp3Decode: DMA out of sync (expected TC, got HT)¥r¥n");
		nResult = -3;
		break;
	}
	else{
		g_pMp3DmaBufferPtr = g_pMp3DmaBuffer + (MP3_DMA_BUFFER_SIZE/2); /* 16bit address pointer calc */
	#if defined(STM32H7XX)
		SCB_CleanDCache_by_Addr((uint32_t*)g_pMp3DmaBufferPtr, MP3_DMA_BUFFER_SIZE);
	#endif
	}
}
else /* unDmaBufMode == HALF_RINGBUF */
{
	/* Check LastHalf Transfer in FirstHalf Mode */
	if(em & TRANSFER_LAST_HALF)
	{
		MSG_PRINTF("Mp3Decode: DMA out of sync (expected HT, got TC)¥r¥n");
		nResult = -3;
		break;
	}
	else{
		g_pMp3DmaBufferPtr = g_pMp3DmaBuffer;
	#if defined(STM32H7XX)
		SCB_CleanDCache_by_Addr((uint32_t*)g_pMp3DmaBufferPtr, MP3_DMA_BUFFER_SIZE);
	#endif
}お次はこちら。
半分転送完了割り込み後の処理で次の転送予定のバッファにCleanをかけます。
g_pMp3DmaBufferPtrに次に転送するバッファのアドレスをコピーした後Cleanです。
g_pMp3DmaBufferPtrはuint16_tのポインタなので注意が必要です。
●DMAじゃないけどLTDCもキャッシュと競合する
ライトスルーが使えなくなった弊害はDMAと同じくキャッシュコヒーレンシーを
求められるLTDCにも波及します。
static void disp_blt (
	int left,				/* Left end (-32768 to 32767) */
	int right,				/* Right end (-32768 to 32767, >=left) */
	int top,				/* Top end (-32768 to 32767) */
	int bottom,				/* Bottom end (-32768 to 32767, >=right) */
	const uint16_t *pat		/* Pattern data */
)
{
	int yc, xc, xs;
#if !defined(USE_TFT_FRAMEBUFFER)
	int xl;
	uint16_t pd;
#endif
	if (left > right || top > bottom) return; 	/* Check varidity */
	if (left > MaskR || right < MaskL  || top > MaskB || bottom < MaskT) return;	/* Check if in active area */
	yc = bottom - top + 1;			/* Vertical size */
	xc = right - left + 1; xs = 0;	/* Horizontal size and skip */
	if (top < MaskT) {		/* Clip top of source image if it is out of active area */
		pat += xc * (MaskT - top);
		yc -= MaskT - top;
		top = MaskT;
	}
	if (bottom > MaskB) {	/* Clip bottom of source image if it is out of active area */
		yc -= bottom - MaskB;
		bottom = MaskB;
	}
	if (left < MaskL) {		/* Clip left of source image if it is out of active area */
		pat += MaskL - left;
		xc -= MaskL - left;
		xs += MaskL - left;
		left = MaskL;
	}
	if (right > MaskR) {	/* Clip right of source image it is out of active area */
		xc -= right - MaskR;
		xs += right - MaskR;
		right = MaskR;
	}
	Display_rect_if(left, right, top, bottom);	/* Set rectangular area to fill */
#if defined(STM32F7XX) || defined(STM32H7XX) /* Flush Cache Datas */
	SCB_CleanDCache_by_Addr((uint32_t*)pat, xc*yc*2);
#endif
#if defined(USE_TFT_FRAMEBUFFER)
	Display_wr_block_if((uint8_t*)pat, xc*yc);
#else
	do {	/* Send image data */
		xl = xc;
		do {
			pd = *pat++;
			Display_wr_dat_if(pd);
		} while (--xl);
		pat += xs;
	} while (--yc);
#endif
}↑ts_fileload.cにて
Cha'N氏のTinyJPEGライブラリででコード後のデータをブロック転送するときにClean
しないと化けます。DMA2DだけじゃなくCPUが介在するFIFOコピーでも化けます。
大量のデータを一気に吐かせるimgファイル(動画データ)ならその都度キャッシュも
総入れ替えとなるので化けないようですが中途半端に転送サイズが小さいとだめな
ようですね。本来なら動画再生のほうもClean突っ込んでやるのが筋なんですけど。
なお、前回触れたライトアロケートキャッシュ設定にしてしまった場合は逆汚染に
よってせっかく書き込んだデータが潰されてしまうためデータの書き込み都度に
Cleanをしてやらないといけなくなってしまいこれによるオーバーヘッドで見かけの
処理速度が大きく落ちてしまいます。
前回も言いましたがSAIでもビットレート上がるとプチプチノイズ入りまくり使い物に
なりませんしやはりSTM32でライトアロケート設定は使わないのが無難です。
というわけで癖が非常に強いSTM32H7ですが、その特性を把握して上手に
コントロールすれば本来の性能をいかんなく発揮できることがわかりました。
STM32F7で十分と思い、食わず嫌いしてましたがSTM32H7、良い感じです!
  
   - 免責・連絡先は↑のリンクを 
 
 ↓SNSもやってます↓
 
  powered by まめわざ powered by まめわざ  - ARM/STM32 (119)
- OpenOCD (27)
- ARM/NxP (34)
- ARM/Cypress (5)
- ARM/Others (3)
- ARM/Raspi (1)
- AVR (13)
- FPGA (4)
- GPS/GNSS (20)
- MISC (86)
- SDCard_Rumors (1)
- STM8 (2)
- Wirelessなアレ (16)
- おきぱ (1)
- ブラウザベンチマーク (29)
- 日本の自然歩道 (27)
   - GNSSモジュールを試用する21 -SAM-M10Qが壊れた…!?と思ったら直せた(おまけあり)-
 ⇒ Kenji Arai (05/29)
- GNSSモジュールを試用する21 -SAM-M10Qが壊れた…!?と思ったら直せた(おまけあり)-
 ⇒ ねむい (05/26)
- GNSSモジュールを試用する21 -SAM-M10Qが壊れた…!?と思ったら直せた(おまけあり)-
 ⇒ Kenji Arai (05/24)
- 中部北陸自然歩道を往く -砺波平野の県境を駆け抜ける!-
 ⇒ ねむい (12/18)
- 中部北陸自然歩道を往く -砺波平野の県境を駆け抜ける!-
 ⇒ ひかわ (12/15)
- STM32U0はぢめました
 ⇒ ねむい (08/07)
- STM32U0はぢめました
 ⇒ ひかわ (07/28)
- STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
 ⇒ ねむい (05/17)
- STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
 ⇒ どじょりん (05/16)
- STM32H5を使ってみる3 -待ち受ける初見殺しの罠たち-
 ⇒ どじょりん (05/16)
   - October 2025 (1)
- September 2025 (1)
- August 2025 (1)
- July 2025 (1)
- June 2025 (1)
- May 2025 (1)
- April 2025 (1)
- March 2025 (1)
- February 2025 (1)
- January 2025 (1)
- December 2024 (2)
- November 2024 (1)
- October 2024 (1)
- September 2024 (1)
- August 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (1)
- April 2024 (1)
- March 2024 (1)
- February 2024 (2)
- January 2024 (1)
- December 2023 (4)
- November 2023 (2)
- October 2023 (2)
- September 2023 (1)
- August 2023 (2)
- July 2023 (1)
- June 2023 (2)
- May 2023 (3)
- April 2023 (1)
- March 2023 (1)
- February 2023 (1)
- January 2023 (1)
- December 2022 (2)
- November 2022 (1)
- October 2022 (1)
- September 2022 (1)
- August 2022 (1)
- July 2022 (1)
- June 2022 (1)
- May 2022 (1)
- April 2022 (1)
- March 2022 (1)
- February 2022 (1)
- January 2022 (1)
- December 2021 (2)
- November 2021 (2)
- October 2021 (1)
- September 2021 (1)
- August 2021 (1)
- July 2021 (1)
- June 2021 (1)
- May 2021 (1)
- April 2021 (1)
- March 2021 (1)
- February 2021 (1)
- January 2021 (1)
- December 2020 (3)
- November 2020 (1)
- October 2020 (1)
- September 2020 (1)
- August 2020 (1)
- July 2020 (1)
- June 2020 (2)
- May 2020 (1)
- April 2020 (1)
- March 2020 (1)
- February 2020 (1)
- January 2020 (1)
- December 2019 (3)
- November 2019 (1)
- October 2019 (1)
- September 2019 (2)
- August 2019 (1)
- July 2019 (1)
- June 2019 (1)
- May 2019 (1)
- April 2019 (1)
- March 2019 (1)
- February 2019 (1)
- January 2019 (1)
- December 2018 (3)
- November 2018 (2)
- October 2018 (1)
- September 2018 (1)
- August 2018 (1)
- July 2018 (1)
- June 2018 (1)
- May 2018 (1)
- April 2018 (2)
- March 2018 (1)
- February 2018 (1)
- January 2018 (1)
- December 2017 (2)
- November 2017 (2)
- October 2017 (1)
- September 2017 (1)
- August 2017 (1)
- July 2017 (1)
- June 2017 (1)
- May 2017 (1)
- April 2017 (1)
- March 2017 (2)
- February 2017 (2)
- January 2017 (2)
- December 2016 (7)
- November 2016 (2)
- October 2016 (2)
- September 2016 (1)
- August 2016 (1)
- July 2016 (1)
- June 2016 (1)
- May 2016 (2)
- April 2016 (1)
- March 2016 (2)
- February 2016 (1)
- January 2016 (1)
- December 2015 (3)
- November 2015 (1)
- October 2015 (3)
- September 2015 (2)
- August 2015 (2)
- July 2015 (3)
- June 2015 (3)
- May 2015 (4)
- April 2015 (2)
- March 2015 (4)
- February 2015 (1)
- January 2015 (3)
- December 2014 (3)
- November 2014 (2)
- October 2014 (1)
- September 2014 (2)
- August 2014 (2)
- July 2014 (3)
- June 2014 (2)
- May 2014 (1)
- April 2014 (1)
- March 2014 (4)
- February 2014 (4)
- January 2014 (3)
- December 2013 (5)
- November 2013 (4)
- October 2013 (3)
- September 2013 (2)
- August 2013 (2)
- July 2013 (2)
- June 2013 (3)
- May 2013 (2)
- April 2013 (2)
- March 2013 (2)
- February 2013 (2)
- January 2013 (3)
- December 2012 (4)
- November 2012 (2)
- October 2012 (2)
- September 2012 (4)
- August 2012 (1)
- July 2012 (3)
- June 2012 (2)
- May 2012 (3)
- April 2012 (3)
- March 2012 (2)
- February 2012 (3)
- January 2012 (3)
- December 2011 (5)
- November 2011 (3)
- October 2011 (2)
- September 2011 (2)
- August 2011 (2)
- July 2011 (2)
- June 2011 (2)
- May 2011 (2)
- April 2011 (2)
- March 2011 (2)
- February 2011 (2)
- January 2011 (3)
- December 2010 (7)
- November 2010 (1)
- October 2010 (1)
- September 2010 (1)
- August 2010 (3)
- July 2010 (4)
- May 2010 (1)
- April 2010 (2)
- March 2010 (2)
- February 2010 (2)
- January 2010 (3)
- December 2009 (3)
- November 2009 (8)
- October 2009 (7)
- September 2009 (5)
- August 2009 (4)
- July 2009 (6)
- June 2009 (6)
- May 2009 (14)
- January 1970 (1)
     
 
Copyright(C) B-Blog project All rights reserved.



