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の
問題に切り込んでいこうと思います!

STM32H7はぢめました・・・本当にいまさら


キタ━━━(゚∀゚)━━━!!
まってたぜぇ…この瞬間(とき)をよぉ…!

出る出る詐欺で一向に出る気配がなかったSTM32HのDiscoveryがついに私の
手元に入りました…!本当に長かった…!(※正式名称はSTM32H747I-DISCO)



基板の形状はSTM32F769I-Discoveryとほぼ同一ですね


メインCPUはH7シリーズ第二世代のSTM32H747XIH6です。第2世代になって何が
変わったかというとなんとCortex-M4コアがもう一つ付いたデュアルコア構成と
なっております!!!!


そんでもってビルトインされたSTLinkも最新のV3仕様で使用CPUはSTM32F723です!
なんとオンチップでUSB-Highspeedの物理層が乗っかっているすごい奴なのです!!


SDRAMはISSI製の32Bitデータバスの32MByteの奴が乗っかっています。


液晶を外して基板表面も見てみましょう。液晶モジュールはSTM32F769I-Discoveryと
全く同じMIPI-DSI接続のWVGAなTFT-LCDです(容量性タッチパネルICも同じ)


LANの物理層はおなじみMicrochipのLAN8742Aです。


そしてこの多CHフォトカプラみたいなのなに…と思ったのですがこれがQSPI-ROM
なのです!マイクロンのMT25QL512ABB8ESF-0SITが乗っています。


そして基板の反対側にもついている。STM32H747XIでは2つのQSPI-ROMを一つの大
容量クアッドSPI-ROMのように使えるDUAL-QUAD Modeが搭載されております!


I2S-CodecはおなじみのWM8994Eです。


そしてArduinoスタイルのコネクタの他にもI/Oを外部に拡張できるコネクタが
新設されました!Xbeeとかをそのままつけられそうですね〜



ぜんぜんピン間隔ちがってたorz


外観はこの辺にしておいてUSBMicroケーブルぶっ刺して通電です!
STMicroがイチオシするSTemWinとTouchGFXをふんだんに使用したでもアプリが
起動します。


TouchGFXはSTM32F769I-Discoveryの時のようにCPUの負荷状態とか見られる機能や
JPEGやDMA2Dみたいな各種ハードウエアアクセラレーションの効果を見ることが
できるプログラムもあり…おや…これは…


ク ソ ゲ ー 再 び
モーやってられませんわこれ(←律儀にやる人


さて、STM32H7でもいつものベアメタルでとりあえず組んでみる予定です。
現在はLチカにはぢまってねむいさんのコンテンツでは欠かせないDMA動作を絡めた
FatFsの実装まで完了しております(画像はLED点滅しているところ)。

H7に上がって機能も制約もF7とは段違いに変わってますので実装を進めながら
次回以降にみっちりと解説していけたらと思います!

Go to top of page