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.
*/
SCB_CleanInvalidateDCache_by_Addr((uint32_t*)buff, count * SECTOR_SIZE);

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) */
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のコアの速度が480MHzも出るので多分気には
ならないとおもいます(投げやり)



●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.
*/
SCB_CleanDCache_by_Addr((uint32_t*)buff, count * SECTOR_SIZE);

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 {
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、良い感じです!

STM32H7を使ってみる4 -キャッシュ・ワンダリング(中篇)-

←前回                       次回→

自宅のPC環境を完全にWin10に切り替えました。
ハードウエアも11年ぶりに新調してメモリも16GBも乗っけてます!!!!
システムディスクも最新のM.2SSDに換装してこれで向こう20年は戦うことが
できるでしょう!!!!!


さて、今回は前回説明した各種キャッシュ設定がパフォーマンスにどう影響
してくるのかを実動作を交えて説明していこうとおもいます。


比較ということでlibpng(倍精度浮動小数点演算あり)のデコード/表示時間を計測して
いきます。テスツ用png画像は自作の虹裏的美少女イラスツを使用してまいります!
突っ込みは受け付けません!!いないさんかわいい!!1!!!1!


また、png画像を読み出すストレージは以前紹介した産業用MicroSDの64GB品を使用
します。安くて信頼性があって使い勝手が良いので重宝します♥


●キャッシュ無し

まずはデータキャッシュそのものを無効にします。
本当はAXI-SRAMだけ無効にしたかったのですがなぜかpngのデコード時にHardFaultに
なってしまったので仕方なくぜんぶキャッシュOFFにしてます。

キャッシュ効いてないせいで遅いのですがAXIバスのクロックが200MHzと高速なので
それでも十分"早い"ですね…

●ライトバック&ライトアロケートなし

こちらはいつもので公開しているキャッシュ設定となります。
ライトスルーのエラッタが認知されてしまった今これに頼らざるを得ません。

●ライトバック&ライトアロケートあり


ライトアロケートなしとほとんど変わらず誤差程度です。

これならライトアロケートありでいいじゃんとお思いでしょうが…


残念ながらLTDCと相性が非常に悪くLTDCが管轄するフレームバッファ領域にこの
設定を行っているとご覧の通り表示が崩れてしまいます…
一応対策できないこともないですがキャッシュコントロール関数をあちこちに放り
こまないといけないため全く現実的ではないです。

それならSDRAMにはその設定しないでAXI-SRAMだけにすればとお思いででしょうが
ねむいさんもそうしようと思ったのですがなんとSAIとも相性が悪く高ビットレートの
音声再生時にブチブチノイズが入りやがりますorzこちらもあちこちでキャッシュコント
ロールしまくったら問題ないのですがそんなんするくらいならライトバック+ライト
アロケートなしのほうでいいやとなり今に至った次第です。

●ライトスルー

今となってはエラッタで禁じ手になったライトスルーモードです。
ライトバックより遅いですがキャッシュ無しより断然早い理由は実メモリに書き込みに
いったときにライトバッファが効いて速度が上がっているからです。
いちいちcleanしないで済むから楽だったんですけどねライトスルー…



●おまけ・カリカリチューンのF7

STM32F769I-Discoでも同じMicroSDを使ってやってみました。
フレームバッファを除くDMAの送受信バッファ・各変数・ヒープ領域をキャッシュ
しなくても超高速に動作できるDTCMに設定して同じようにやってみたのがこちら。
H7と比べるとコアクロックが1/2の200MHz動作なのですが意外と健闘してます。
F7シリーズではDTCM領域でもDMAできるのでこのような芸当ができるのですが
H7は直接できなってしまいましたのでライトスルーが使えなくなったのもあって
DMAするだけでもいろんな工夫が必要です。

次回はSTM32H7でキャッシュを効かせた状態でDMAをどのように安定して動かして
いけば良いのかを解説していこうと思います。


20191104追:
jujurouさんがF7ではSDRAMのライトバック+ライトアロケートなしでもDMA2Dで
表示おかしくなると報告されているので
ねむいさんも追って知らべてみました…

が…(STM32F746G-Discoveryで試した)


ううむねむいさんのいつものじゃどういう状況でもデータ化けしてぬぇ…
※9月度版の時点でライトバック版に変更済みです。


キャッシュを効かせたRAM領域にフレームバッファを確保してLTDCを動作させている
時点でDMAしてる時と同じようになりDMA2DせずともキャッシュとLTDCコントローラとの
干渉が発生してしまいます。これはライトアロケートあるなしに関係はないので
ライトバックではデータ転送前に必ずキャッシュのCleanが必要となります。

ねむいさんのいつものでこれに引っかかった例がChaNさんのjpegライブラリを使い
jpegファイルを表示させようとした時で、disp_blt関数内で転送直前にクリーンを
行うことにより対策しております。

そして前回も簡単に説明しましたがキャッシュコントロールをしているにもかかわらず
ライトアロケート時に表示が崩れてしまう原因は書き込みキャッシュミス時に実メモリ
からデータを読み出しキャッシュラインに展開している動作そのものにあります。
実メモリ(SDRAM)からのread動作によってキャッシュラインが逆汚染され、結果として
寄生Evictionが起こりまくり表示が崩れてしまうことになります。
データキャッシュサイズ上限以下の容量でチマチマ書き換える…たとえばFONTX2の
表示とかの時に一番崩れやすくなります。

↑こんな感じに崩れます。
これはLTDCに限らずSAIとかの基本書き込みっぱなしのぶっ放しのペリフェラルでも
起こりますが長くなりそうなので次回にでも解説します。

STM32H7を使ってみる3 -キャッシュ・ワンダリング(前篇)-

←前回                       次回→

今から4年以上前、STM32F7ではぢめてキャッシュという概念に対峙し、分かった
つもりでお茶を濁してきましたがH7になった今どうしても正面から向き合わざるを
得ない事態になってしまいました…それも最悪な形で!!

●Cortex-M7のキャッシュにエラッタ発覚!

オイオイオイ死ぬわ私。(一般には今年春くらいに知れ渡った内容です)
要約しますとCortex-M7系マイコンでキャッシュポリシーをライトスル―にした状態で
Double-WORD(ARMの場合8バイト)の読み書きを同一のアドレスで行った時に、読み
出しの時にデータが壊れる事があるという内容…しかも現行の全リビジョン…
そしてカテゴリーAとかいうフュージョンジャックした剣崎さんでも不覚をとりかねない
クリティカルっていうか実質使用禁止設定判定なエラッタ…すざけんな!!111!
全文はこちらで読めますので各自把握しておいてください。

おきぱのSTM32F7/H7向けFatFsデモはライトバックを使用するように
すでにコードを修正済ですので安心してお使いいただけます。


STM32F7/H7側の障害情報ではF7はもちろん最新のH745/755に至るまで当エラッタが
網羅され、全滅状態ですorz


つまり、STM32F7/H7でキャッシュを使用する際はキャッシュの取り扱いが面倒な
ライトバック以外にキャッシュポリシーがとれないことを示しますorz

ぁーぁ…


●ひとまずSTM32H7のキャッシュのおさらいっ
まずはけしかん様のブログ記事も熟読の上で…

STM32F7/H7に搭載されてたキャッシュメモリは命令(I)とデータ(D)用に独立した
キャッシュが設けられております。キャッシュの方式はI/D両者ともセット・アソシエイティブ
という回路規模とキャッシュ性能を折半した方式が採られていて、とりわけデータ用
では4ウェイセット・アソシエイティブという方式となっております。
ウエーイが4つもあってどんだけリア充なんだYO!

そしてキャッシュポリシーとしてはSTM32F7/H7は下記の3タイプ存在します。
1.ライトスルー(ライトアロケート無し固定)
 キャッシュに書き込みと同時に実メモリにも常に書きこむ。
 キャッシュの取り扱いは簡単だが動作は一番遅い。
 使い勝手一番良かったのにエラッタのせいで使用不能!!11!1

2.ライトバック・ライトアロケート有り
 キャッシュラインがいっぱいになるかCleanしないと実メモリに書きこまない。
 書き込み時キャッシュミスした場合1ライン分実メモリから読みだして
 キャッシュラインに展開する。ちょっと遅い。
 頻繁に読み書きする変数領域向けなのでフレームバッファのような書きっぱの
 用途では著しく不利になるので設定してはならない。

3.ライトバック・ライトアロケート無し
 キャッシュラインがいっぱいになるかCleanしないと実メモリに書きこまない。
 書き込み時キャッシュミスした場合も実メモリから読みだす動作はしない。
 理論上一番早い。ライトスルーが封じられた今これが一番無難。

1〜3のいずれも読み込み側動作(リードアロケート)はすべて同様に行われる。

1.はエラッタのせいで死に設定なので2.や3.を選びます。尤もライトバック・ライト
アロケートありでもmemset等の関数で同じデータを書いていくような動作を検知して
自動的にライトアロケート無しに動的に切り替わりキャッシュが動くらしいです
(↑ほんまかいな)

詳しい解説はAPSさんのサイトにあります。
Cortex-Aのキャッシュについての解説ですがCortex-M7も動作や設定は同一です。


●で、具体的な設定はどうすればよいのか
Cortex-M7に置いてはMPU(メモリプロテクションユニット)からキャッシュポリシーの
設定を行います。ねむいさんのいつもののhw_config.cにあるMPU設定のサブルーチン
より、AXI-SRAMの設定を例に紹介します。

	/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AXI-SRAM D1 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x24000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
AXI-SRAMの512kByte丸々ライトバック・ライトアロケート無しの設定をする場合
上記のようにします。
MPU_InitStruct.IsBufferable 	= MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;

↑特にこの3行は重要です。ライトバッファ有効・キャッシュ有効でライトバック
となります。また、SHAREABLEの設定は必ずMPU_ACCESS_NOT_SHAREABLEにしておいて
ください!ここをMPU_ACCESS_SHAREABLEとしてしまうとキャッシュ無効にしたのと
同じになってしまいます(次回実動作を交えて解説) 実に罠設定です。

ちなみにこの超重要な情報はNXPのM7コアのマイコンiMXのアプリケーションノート
明記されております。
しかしSTのマニュアルやアプリケーションノートには何所にも全く書いておらず
罠にはまってキャッシュの効力殺してるの気づいてない人多いんじゃないかと☠



SHAREABLE有効にしつつキャッシュ動作を有効させる方法もありますが今回のシステム
ではまだ利用価値が無いので割愛します。

また、ライトアロケート有りで行きたい場合は
MPU_TEX_LEVEL0->MPU_TEX_LEVEL1に変更します。
でもCortex-M7コアでライトアロケートありするくらいならエラッタ付きライトスルーに
した方が10000000000倍マシです☠
こちらの比較も次回以降解説しますね。

	/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AHB-SRAM1 D2 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AHB-SRAM2 D2 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30020000;
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AHB-SRAM3 D2 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30040000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER3;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* Configure the MPU attributes as Write-Back/No-Write-Allocate for AHB-SRAM4 D3 */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x38000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_64KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER4;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

残りの内蔵SRAMの設定もライトバック・ライトアロケート無しの設定としました。
AHB-RAMはAXIバスマトリクス上にはないので意味なさそうですが実はキャッシュの
恩恵にあずかることができます。

	/* External-Memories cache setting */
/* Configure the MPU attributes as Write-Back/No-Write-Allocate for External SDRAM */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0xD0000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_32MB; /* STM32H747I-Disco have 32Mbyte SDRAM */
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER6;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0; /* MUST be No-WriteAllocation to avoid collision LTDC-Hostage */
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

/* External-Memories cache setting */
/* Configure the MPU attributes as Write-Back for QSPI-DirectRemapping(ReadOnly) */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x90000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_128MB; /* STM32H747I-Disco have 64*2=128MByte QSPI-ROM */
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RO_URO;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER7;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);

外部メモリ領域のSDRAMやQSPIはAXIバスでつながっているので当然のことながら
大きな影響を受けます。しっかりキャッシュ設定をしておきましょう。
また、SDRAMをLTDCのフレームバッファに使用したい場合は必ずライトアロケーション
なしを選択してください。ライトアロケーションありだといちいちCleanをしてやる必要が
出来てしまい、それを怠るとDMAの時のようにデータの一貫性が保たれなくなり表示が
おかしくなります。フレームバッファはデータを読みだすことはほぼ無く書きっぱなし
なのでライトアロケーションなしの方が都合が良いのです。
QSPIはメモリマップドモードのリードオンリーで使用するためどうでもよいですが
AccessPermission以外はSDRAMとキャッシュ設定を合わせておきましょう。

そしてSAI等の周期的にデータ投げる用途ではAXI-SRAMをライトアロケート有りにして
いるとキャッシュコントロールを今よりさらに厳格にしないと高ビットレートでポツポツノイズが
入りやがることが分かったので
もう全部一律ライトアロケート無しでやります!!!!!!!!!

次回は上記キャッシュポリシーの設定別に実動作をさせたときの性能の比較を行って
みたいと思います!

STM32H7を使ってみる2 -スライムが強すぎてアリアハンから出られない件-

おおねむいよしんでしまうとはなさけないっ…ていうかさ、お前このぶろぐ立ち上げて
10年間ずっと何やってきたんだ?毎年初心に帰ってないでさっさと先に進めや!!1!と
王様に副業先とほぼ同じ内容(本業は虹裏メイド)で怒られてる今現在でございますが
STM32って言う名前のスライムなんですがこれ世間一般の認識の青色のプルプル形状してて
ワンパンで死ぬ雑魚
と違ってねむいさんの前に立ちはだかるそいつは液状で高速で
動き、物理攻撃が効かず強力な"溶かす"攻撃してくる最強クラスの敵なんですがねむい
さんのほうがワンパンで死んでしまうのですがこいつのせいでFatFsの実装という最初期
段階で踏みとどまって10年が経過してしまったのですが年数の経過とともにねむいさん
倒しまくって経験値増やしていった結果スライム君の方が先にレベルアップしてしまって
STM32F4とかF7とかどんどん数字が上がって挙句の果てにクロック周波数480MHz
デュアルコアH7とかにパワーアップしてしまったスライムに包囲されアリアハンは壊滅
寸前もうだめだでもよく考えたらねむいさんの職業て勇者じゃなくて虹裏メイドだから
別に無理してアリアハンから出なくてもいいんじゃねっていう弱い考えが浮かびそうに
なりますが今度こそスライムを倒すために異世界モノなろう小説風に毎回恒例の初心
表明を宣言するのでした!10年後に転生したら10年たっていた件


さて、謎の文字列は置いといてSTM32H747I-Discovery向けいつものはこちらになります。
まだ暫定版なので明らかなバグ以外はご意見無用とさせていただきます!!!!

上記のブツを元にプログラムの解説をしていきますのでよろしくお願いします。

●アリアハン脱出作戦開始

まずすべきことはSTM32H7のメモリ/バスのアーキテクチャを理解してプログラム作りの
土台を作ること、つまりはH7向けのリンカスクリプトとビルド環境を構築することです。


STM32F7に比べてメモリ構成はさらに複雑になっています。しかもデュアルコアなので
それを意識してメモリを分け分けしないといけません。
ねむいさんの戦略としては当分の間はH7コアをシングルで動かすことを主眼に入れ、
上記画像のように組んでみました。H7コアと同じ周波数で動作できる密結合メモリの
DTCM-RAMはほぼスタック専用に、大容量&バス幅64bitのAXI-SRAMはDATA,BSS,HEAPに
割り振ります。ITCM-RAMも容量が増えたので今回から積極的に使用していきます!
そしてAHBバスマトリクスに配置されているSRAMですがこちらはバスマトリクスがどこで
繋がってるかをしっかりわかっておかないとDMAとかでハマるので以後個別に詳しく
解説しようと思います。

また、すでに先達の使用者からよく言われていることですがF7では便利だったDTCMは
H7に上がってDMAの制約が極めてきつくなり、実質上直接DMA不可となっていますF**K!!!!
一応やれないこともないのですがMasterDMAを介した面倒な間接転送となります。
これについてもいずれ別記事で詳しく解説します。


/* Linker script Upperside for STM32H747XIH6		*/
/* Nemui Trinomius (http://nemuisan.blog.bai.ne.jp) */

OUTPUT_FORMAT ("elf32-littlearm")

/* Memory Spaces Definitions */
MEMORY
{
/* Instruction TCM SRAM */
ITCM_RAM (xrw) : ORIGIN = 0x00000000, LENGTH = 64k
/* Data TCM SRAM */
DTCM_RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128k
/* AXI-SRAM D1 Domain */
AXI_SRAM (xrw) : ORIGIN = 0x24000000, LENGTH = 512k
/* AHB-SRAM1 D2 Domain */
SRAM1_D2 (xrw) : ORIGIN = 0x30000000, LENGTH = 128k
/* AHB-SRAM2 D2 Domain */
SRAM2_D2 (xrw) : ORIGIN = 0x30020000, LENGTH = 128K
/* SRAM3 D2 Domain */
SRAM3_D2 (xrw) : ORIGIN = 0x30040000, LENGTH = 32k
/* SRAM4 D3 Domain */
SRAM4_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64k
/* Backup SRAM */
BKP_SRAM (rw) : ORIGIN = 0x38800000, LENGTH = 4k
/* External SDRAM(FMC) */
EXT_SDRAM (xrw) : ORIGIN = 0xD0000000, LENGTH = 32M
/* Main Embedded Dual-Bank FlashROM */
FLASH_ROM (rx) : ORIGIN = 0x08000000, LENGTH = 2M
/* External Dual-Mode QSPI-ROM */
QSPI_ROM (rx) : ORIGIN = 0x90000000, LENGTH = 128M
}

/* higher address of the stack bottom,AAPCS said "stack MUST BE 8byte alignment". */
_estack = (ORIGIN(DTCM_RAM)+LENGTH(DTCM_RAM)) & ~ 0x7;

/* higher address of the heap end */
_eheap = ORIGIN(AXI_SRAM)+LENGTH(AXI_SRAM);


/* include the section management sub-script */
INCLUDE "STM32H7xxxx_FLASH.ld"


こんな感じでリンカスクリプトに落としました。
今回はスクリプトそのものも見直して意味不明なことやってた記述は排除/修正
しております!その修正内容は…


ねむいさんスタックポインタとかヒープの設定で9年間くらい謎の引き算やってた…
なんでこんなんでアライメントが揃うと思っていたのか…
(↑ARM7TDMIは設定するSRAMの最終アドレスから-8バイト引いた値をspに設定しないと
  いけなかったのでCortex-Mからスタック設定の仕様が変わった後も仕様をよく見
  ないでやっていたと考えられる…これじゃスライムにやられるわけだ)


●クロック設定とかGPIO入出力とか
こちらに関してはSTM32H7CubeのBSPを参考にすれば問題はありません。
最大480MHzで動作するH7コアですがエラッタやSDRAMを使った時の速度的な制約があり
H7コアクロック400MHz、AXI/AHBバスクロック200MHzがつぶしが効く設定となり、BSP
のExampleではすべてこのクロック周波数設定となっています。
これをそっくりそのまま頂いちゃいましょう!
ぇ…?CubeMX?なんスかそのバグコード生成機
/* Defines -------------------------------------------------------------------*/
/**
* @brief System Clock Configuration
* The system Clock is configured as follow :
* System Clock source = PLL (HSE)
* SYSCLK(Hz) = 400000000 (Cortex-M7 CPU Clock,upto 480MHz)
* HCLK(Hz) = 200000000 (Cortex-M4 CPU, Bus matrix Clocks,upto 200MHz)
* AHB Prescaler = 2 (AHB/AXI Bus Matrix Clock upto 200MHz)
* D1 APB3 Prescaler = 2 (APB3 Clock upto 100MHz)
* D2 APB1 Prescaler = 2 (APB1 Clock upto 100MHz)
* D2 APB2 Prescaler = 2 (APB2 Clock upto 100MHz)
* D3 APB4 Prescaler = 2 (APB4 Clock upto 100MHz)
* HSE Frequency(Hz) = 25000000
* VCO Frequency = 800000000 (160*5)
* PLL_M = 5
* PLL_N = 160
* PLL_P = 2
* PLL_Q = 4
* PLL_R = 2
* VDD(V) = 3.3
* Flash Latency(WS) = 4
* @param None
* @retval None
*/
#define PLL1_M 5
#define PLL1_N 160
#define PLL1_P 2
#define PLL1_Q 4
#define PLL1_R 2
#define PLL1_FRACN 0
上記はコア/バス/ペリフェラルクロック用のPLL設定値のdefineです。
F7と比べるとPLLが増えてかなり変わってる感じがしますね。
hw_config.cに具体的なコードがあるのでご参考に。
また、ユーザーマニュアルではカーネルクロックなるややこしい概念が出てきますが
殆どのペリフェラルではHCLK(AHB)のクロックを指します。

static void LED_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

/* Enable GPIO_LED clock */
GPIO_LED_CLKEN();

/* Configure GPIO for LEDs as Output push-pull */
GPIO_InitStructure.Pin = LED_D1;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStructure.Alternate = 0;
HAL_GPIO_Init(GPIO_LED1, &GPIO_InitStructure);
LED_D1_OFF();

GPIO_InitStructure.Pin = LED_D2;
HAL_GPIO_Init(GPIO_LED2, &GPIO_InitStructure);
LED_D2_OFF();
GPIO_InitStructure.Pin = LED_D3;
HAL_GPIO_Init(GPIO_LED3, &GPIO_InitStructure);
LED_D3_OFF();
GPIO_InitStructure.Pin = LED_D4;
HAL_GPIO_Init(GPIO_LED4, &GPIO_InitStructure);
LED_D4_OFF();
}

LED(GPIO)の出力設定はF7とほぼ変わりませんが100MHz越えのI/Oトグリング対応の為
"GPIO_SPEED_FREQ_VERY_HIGH"という定義が追加されてます。特に必要が無い場合は
VERYHIGHにしておきましょう。

static void KEY_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;

/* Enable GPIO_LED clock */
RCC->AHB4ENR |= (RCC_AHBPeriph_GPIO_KEY_USER1);
RCC->AHB4ENR |= (RCC_AHBPeriph_GPIO_KEY_SEL);
RCC->AHB4ENR |= (RCC_AHBPeriph_GPIO_KEY_DOWN);
RCC->AHB4ENR |= (RCC_AHBPeriph_GPIO_KEY_LEFT);
RCC->AHB4ENR |= (RCC_AHBPeriph_GPIO_KEY_RIGHT);
RCC->AHB4ENR |= (RCC_AHBPeriph_GPIO_KEY_UP);

/* Configure GPIO for Key Input */
GPIO_InitStructure.Pin = KEY_USER1;
GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
GPIO_InitStructure.Pull = GPIO_NOPULL;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_VERY_HIGH;;
GPIO_InitStructure.Alternate = 0;
HAL_GPIO_Init(GPIO_KEY_USER1, &GPIO_InitStructure);

GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Pin = KEY_SEL_PIN;
HAL_GPIO_Init(GPIO_KEY_SEL, &GPIO_InitStructure);
GPIO_InitStructure.Pin = KEY_DOWN_PIN;
HAL_GPIO_Init(GPIO_KEY_DOWN, &GPIO_InitStructure);
GPIO_InitStructure.Pin = KEY_LEFT_PIN;
HAL_GPIO_Init(GPIO_KEY_LEFT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = KEY_RIGHT_PIN;
HAL_GPIO_Init(GPIO_KEY_RIGHT, &GPIO_InitStructure);
GPIO_InitStructure.Pin = KEY_UP_PIN;
HAL_GPIO_Init(GPIO_KEY_UP, &GPIO_InitStructure);
}

入力についても同様です。
H7-Discvoeryは4+1入力ジョイスティックが追加されたので物理キー入力処理も追加
しております
/* Macros */
/* uncomment to enable joy input */
//#define USE_HWKEY_INPUT_SUPPORT

/* ITCM Fastest call for IRQ */
#define __ITCM __attribute__ ((section (".itcm")))
//#define __ITCM __attribute__ ((long_call, section (".itcm")))

hw_config.h内で上記の物理キー入力有効無効が切り替えられるようにしておきました。
タッチパネルがあるのでデフォルトは無効です。


●タイムベースは重要ですよね
	/* Making MilliSecond-Order Timer */
/* Select Clock Source */
SystemCoreClockUpdate();
HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);
/* Setup SysTick Timer for 1 msec interrupts */
if (SysTick_Config(SystemCoreClock/interval))
{
/* Capture error */
while (1);
}

マイコンを動かすにおいて必要不可欠なタイマですがsystickの設定についてはF7と
殆どかわりません。ほんの少しの修正で使用可能です。
ねむいさんのいつものではミリ秒オーダーのタイムベースとして動作させてます。

	/* Making MicroSecond-Order Timer uses general purpose timer! */
/* Enable timer clock */
USEC_TIMx_CLKEN();

/* calculate TIMx(2~5) Prescaler clock(D2 Domain) */
if(RCC->D2CFGR & RCC_D2CFGR_D2PPRE1){
if((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) == RCC_D2CFGR_D2PPRE1_DIV2){
cal_usec_divide = SystemCoreClock/2; /* (HCLK(=SYSCLK/2)*2)*2 */
}
else if((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) == RCC_D2CFGR_D2PPRE1_DIV4){
cal_usec_divide = SystemCoreClock/4; /* (HCLK(=SYSCLK/2)*4)*2 */
}
else if((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) == RCC_D2CFGR_D2PPRE1_DIV8){
cal_usec_divide = SystemCoreClock/8; /* (HCLK(=SYSCLK/2)*8)*2 */
}
else if((RCC->D2CFGR & RCC_D2CFGR_D2PPRE1) == RCC_D2CFGR_D2PPRE1_DIV16){
cal_usec_divide = SystemCoreClock/16; /* (HCLK(=SYSCLK/2)*16)*2 */
}
}

TimHandle.Instance = USEC_TIMx;
TimHandle.Init.Period = UINT32_MAX;
TimHandle.Init.Prescaler = ((cal_usec_divide)/USEC_INTERVAL) - 1;
TimHandle.Init.ClockDivision = 0;
TimHandle.Init.CounterMode = TIM_COUNTERMODE_UP;
TimHandle.Init.RepetitionCounter = 0;
if (HAL_TIM_Base_Init(&TimHandle) != HAL_OK)
{
/* Capture error */
while (1);
}

USEC_TIMx->CR1 &= ~(TIM_CR1_UIFREMAP); /* Disable UIF Remap(Must Need!) */

HAL_TIM_Base_Start(&TimHandle);

μ秒オーダーは32bitタイマを利用しています。F7/H7でTIM5のフリーランニング動作で
実現しております。ただH7ではAHBの番号が異なっているので変更は必用です。
また、バスクロックも2倍に上がっているのでそれを考慮した計算値を与えましょう。
各詳細はsystick.c参照

●リングバッファ付UARTもはや基本
ねむいさんUARTという文字列をなぜかずっとウラートと呼んでました…ソビエト連邦かな
(正解:ユーアート)

#if defined(USE_STM32H747I_DISCO)
#define UART_DEFAULT_NUM 1
#define UARTx USART1
#define USARTx_IRQHandler USART1_IRQHandler
#define USARTx_CLKEN() ¥
/* Enable GPIO clock(UART_TX) */ ¥
RCC->AHB4ENR |= (RCC_AHB4ENR_GPIOAEN); ¥
/* Enable GPIO clock(UART_RX) */ ¥
RCC->AHB4ENR |= (RCC_AHB4ENR_GPIOAEN); ¥
/* Enable UART clock */ ¥
RCC->APB2ENR |= (RCC_APB2ENR_USART1EN);
#define USART_TxPin GPIO_PIN_9
#define USART_RxPin GPIO_PIN_10
#define USART_TxPort GPIOA
#define USART_RxPort GPIOA
#define USART_TXALT GPIO_AF7_USART1
#define USART_RXALT GPIO_AF7_USART1

/* This is the ARDUINO Option(not used) */
//#define UART_DEFAULT_NUM 8
//#define UARTx USART8
//#define USARTx_IRQHandler USART8_IRQHandler

#else
#error "Select or make board settings!!"
#endif

こちらについてもF7と大きな変更はなく、H7-Discovery内のSTLink/V3のVCPにつなが
っているPA9(TX),PA10(RX)を割り当てるだけです。まぁこのポートはブートローダー
でも使うSTM32伝統のUARTポートみたいな感じですね〜

#if defined(UART_INTERRUPT_MODE)
/**************************************************************************/
/*!
@brief Handles UARTx global interrupt request.
@param None.
@retval None.
*/
/**************************************************************************/
__ITCM void USARTx_IRQHandler(void)
{
uint32_t IntStat = UARTx->ISR;

if(IntStat & USART_ISR_RXNE_RXFNE)
{
/* Clear Errors */
UARTx->ICR = USART_ICR_ORECF | USART_ICR_NECF | USART_ICR_FECF | USART_ICR_PECF;

/* Advance buffer head. */
unsigned int tempRX_Head = ((&USARTx_Buf)->RX_Head + 1) & (UART_BUFSIZE-1);

/* Check for overflow. */
unsigned int tempRX_Tail = (&USARTx_Buf)->RX_Tail;
uint8_t data = UARTx->RDR;

if (tempRX_Head == tempRX_Tail) {
/* Overflow MAX size Situation */
/* Disable the UART Receive interrupt */
UARTx->CR1 &= ~(USART_CR1_RXNEIE);
}else{
(&USARTx_Buf)->RX[(&USARTx_Buf)->RX_Head] = data;
(&USARTx_Buf)->RX_Head = tempRX_Head;
}
}

if(IntStat & USART_ISR_TXE_TXFNF)
{
/* Check if all data is transmitted. */
unsigned int tempTX_Tail = (&USARTx_Buf)->TX_Tail;
if ((&USARTx_Buf)->TX_Head == tempTX_Tail){
/* Overflow MAX size Situation */
/* Disable the UART Transmit interrupt */
UARTx->CR1 &= ~(USART_CR1_TXEIE);
}else{
/* Start transmitting. */
uint8_t data = (&USARTx_Buf)->TX[(&USARTx_Buf)->TX_Tail];
UARTx->TDR = data;

/* Advance buffer tail. */
(&USARTx_Buf)->TX_Tail = ((&USARTx_Buf)->TX_Tail + 1) & (UART_BUFSIZE-1);
}
}
}
#endif

また、実際の通信では割り込みを用いてノンブロッキング(非同期)のリングバッファ
構成を取っています。割り込みルーチン本体はITCMにコードを配置して超高速に処理
できるようにしておきました。

/**************************************************************************/
/*!
@brief Copy Time-Critical codes into ITCM-RAM.
@param None.
@retval None.
*/
/**************************************************************************/
static void datacopy(unsigned int romstart, unsigned int start, unsigned int len)
{
unsigned int *pulDest = (unsigned int*) start;
unsigned int *pulSrc = (unsigned int*) romstart;
unsigned int loop;
for (loop = 0; loop < len; loop = loop + 4)
*pulDest++ = *pulSrc++;
}
/* _stitcm,_sitcm,_eitcm are MUST be decrare in likerscript file */
static void ITCM_Datainit(void)
{
unsigned int *LoadAddr, *ExeAddr, *EndAddr, SectionLen;

/* Copy ITCM Codes into ITCM-RAM */
LoadAddr = &_stitcm;
ExeAddr = &_sitcm;
EndAddr = &_eitcm;
SectionLen = (void*)EndAddr - (void*)ExeAddr;
datacopy((unsigned int)LoadAddr, (unsigned int)ExeAddr, SectionLen);
}

当たり前の事ですがITCMで動作させるためにはあらかじめコードを転送しておかないと
いけないので起動直後とかにコードを送っておきましょう。
その他細かい部分はhw_config.cに詰まっていますのでそちらをご参照ねがいます。





というわけでまだほんの少ししか紹介出来ておりませんがひとまずH7を動かすための
基本的な項目の解説を行いました。次回からはH7最大の難関であるキャッシュとDMAの
問題に切り込んでいこうと思います!

Go to top of page