はじめに
最近、ADC の性能を可能な限り極限まで引き出したかったので、関連するペリフェラル機能を調べてみた。
その中でも cache と MPU 属性の合わせ込みは、以前からあまり理解できていなかった部分で、今回ようやく整理できた。cache 機能を持つ ARM チップでは、MPU と組み合わせて設定してはじめて cache を使える。
- キャッシュ(バッファ): 主に、プロセッサコアがメモリをより連続的に操作できるようにするためのものだ。メモリ操作の一部はバスをまたぐため、キャッシュは読み出し時にヒットしたメモリ周辺をプリフェッチしたり、書き込み時に少し遅延させて連続アクセスにまとめたりできる。何より内部バスの競合を減らしてデータ処理を高速化できるのが大きい。
- MPU(Memory Protection Unit): メモリ保護ユニットは ARM チップのメモリ保護機構で、主に特定のメモリ領域のキャッシュ属性を設定するために使う。もちろん他の用途も考えられるが、ぶっちゃけ他に使い道はない。たとえばタスクスレッドのロード状況に応じてメモリ領域の設定を細かく変え、MMU のような機能を実現することもできるが、普通の人はそんな複雑なことはしない。
- DMA(Direct Memory Access): ダイレクトメモリアクセス。プロセッサの演算能力を介さずに、ペリフェラルレジスタの値をメモリへコピーできる。一定量(たとえば 1024 個)コピーすると割り込みを発生させて通知し、そのデータを処理できるようにする。
MPU と MMU の主な違い
| MPU | MMU | |
|---|---|---|
| キャッシュヒット周期 | 固定1命令サイクル | 1〜20サイクル |
| 管理粒度 | 限られた領域(十数個)のみ管理 | RAM をページ単位で細かく管理できる |
| マルチタスク対応 | 物理領域を分割して限定的に分離する | 仮想アドレス空間でプロセスを完全に分離する |
| 要するに | リアルタイム向け | Linux 向け |
ほかにも多くのチップが同様のキャッシュや MPU 機能を持つが、ここで主に扱う ARM チップも機能は似ている。管理粒度、分割できる領域の数、固定属性で管理できるかどうかが違うだけで、属性や対応する機能はどれも似通っている。
バスの理解
MCU チップのペリフェラルは複数のバスに分散して配置されており、同じバス上のペリフェラルとメモリとの間の読み書きは比較的高速だ。ここでは ADC・DMA・メモリの組み合わせだけに絞ってバスを理解しよう。
このアプリケーションでは、DMA は同じバス上のペリフェラルとメモリしか操作できないため、ADC1/2/3・DMA1/2・BDMA1 を使っている。
以下のバス構成図とあわせて理解してほしい。ADC1 は DMA1 と組み合わせてサンプリングし、D2 ドメインメモリの一部を使用する。ADC3 は BDMA と組み合わせてサンプリングし、D3 ドメインメモリの一部を使用する。そして取得したデータを一次処理したあと、最終的なサンプル値を D1 ドメインメモリに置き、CPU が後続の計算を行う。このように割り当てることで、ドメインをまたぐ際の性能低下を最小限に抑えている。

使用したバス構成
キャッシュ属性の設定
MPU の設定がキャッシュ属性を制御する。最も重要な設定項目は次の 3 つで、私なりに簡単にまとめてみた。
- IsCacheable: キャッシュを有効にするかどうかを決める。
- IsBufferable: 書き込み命令もキャッシュバッファにいったん溜めてからメモリへまとめて書き込むかどうかを決める。IsCacheable を有効にしないと意味がない。
- IsShareable: 厳格なタイミング制御を行うかどうかを決める。これは外部ペリフェラル、たとえば FMC が外部メモリへアクセスする場合に重要だ。チップの外までリソースがまたがるため、厳格なタイミング制御が必要になる。
なお、ChatGPT による説明のほうが、私の説明より明確で詳しい。
-IsCacheable は D-Cache を使用するかどうかを制御し、M7 では TEX/C/B と組み合わせてキャッシュ戦略を決める。
- IsBufferable はライトバッファリング/コンバイニング(write buffering/combining)を許可し、外部から見えるタイミングを遅らせる代わりにスループットを高める。
- IsShareable はその領域が「複数のホストから共有され得る」ことを示す。M7 ではキャッシュ戦略も変わり、Cacheable+Shareable は Write-Through/No-Write-Allocate に強制される。
詳細
- IsCacheable
- 注目点: D-Cache を通すかどうか。TEX/C/B と組み合わせて Write-Back/Write-Allocate にするか、Write-Through/No-Allocate にするかを決める。
- 影響: CPU のアクセスレイテンシ、帯域幅、バストラフィックに影響する。ペリフェラルのサンプリングレートを直接変えるわけではないが、CPU の処理速度には影響する。
- IsBufferable
- 意味: 書き込みをまずライトバッファに入れてマージできるようにし、非同期にメモリ/バスへフラッシュすることで、バストランザクションを減らしスループットを高める。
- 影響:
- 性能: 連続書き込み(memcpy、ストリーム書き込み)が高速化する。
- 可視性/順序: 他のホスト(DMA/ペリフェラル)から見た書き込みの「見えるタイミング」が遅れる可能性がある。必要に応じてバリア命令を使い順序を保証する。
- 典型的な使い方: Normal メモリは通常 BUFFERABLE にする。Device/MMIO では並べ替えやマージを避けるため、通常は設定しない。
- IsShareable
- 意味: その領域が複数のホスト(CPU、DMA など)によって共有され得ることを示す。Cortex-M7 では Cacheable な Normal 領域に特に影響する:
- Cacheable + Shareable = Write-Through(ライトスルー)、No-Write-Allocate(ノーライトアロケート)。他のホストが古いデータを見るウィンドウを減らせるが、CPU 側の性能は WB/WA よりやや落ちる。
- Cacheable + Non-shareable = 通常は Write-Back/Write-Allocate(TEX=1,C=1,B=1 との組み合わせ)になり、CPU 性能が最も良い。
- 影響:
- 順序と可視性のセマンティクスがより保守的になり、他のホストとの連携に有利。
- 「ハードウェアによるキャッシュコヒーレンシ」と同義ではないが、キャッシュの書き込み戦略とメモリバリアのセマンティクスの範囲を変える。
よく使う組み合わせ早見表(Normal メモリ)
- B=1, C=1, Shareable=0, TEX=1 → Write-Back, Write-Allocate(CPU 最速で、大半の計算バッファに適する)
- B=1, C=1, Shareable=1, TEX=1 → Write-Through, No-Write-Allocate(より保守的な共有セマンティクス)
- B=0/1, C=0 → Non-cacheable(ライトバッファを許可するか選択可能。B=0 はより厳格なタイミング)
先ほどのバス構成図のとおり、キャッシュは CPU の一部に属するため、DMA が取得するメモリデータ(D2/D3 ドメインのメモリ領域)は必然的にドメインをまたぐ。ドメインをまたぐアプリケーションでは、メモリ一貫性(キャッシュコヒーレンシ)の問題を解決しなければならない。 そのためにメモリ領域サイズ分の while ループで強制的にキャッシュヒットさせて一貫性を解決する必要があり、かえって不要なコストがかかる。
そこで MPU 設定では、D2・D3 ドメインに対応するアドレス範囲ではキャッシュを有効にしない。これは上記の 3 つ目の組み合わせに当たる。D1 ドメインのよく使うメモリ領域では、IsCacheable を有効、IsBufferable を有効、IsShareable を無効にして最大の性能を得る。これは上記の 1 つ目の組み合わせに当たる。
2 つ目の組み合わせは、今のところ使う場面に遭遇したことがなく、想像もつかない。
以下は上記の設定に対応するコードで、参考用と自分の記録用に載せておく。
/**
* @brief MPU配置
* @param None
* @retval None
*/
static void MPUInit(void)
{
MPU_Region_InitTypeDef MPU_InitStruct;
/* 禁止 MPU */
HAL_MPU_Disable( );
/* 配置AXI SRAM的MPU属性为Write back, Read allocate,Write allocate
最佳性能,用于CPU处理计算 */
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_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_ENABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置D2域MPU
D2域外设的DMA使用 禁止cache */
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x30000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256KB;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER1;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置以太网收发描述符部分为Strongly Ordered */
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_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER2;
MPU_InitStruct.SubRegionDisable = 0x0;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置D3域MPU
D3域外设的DMA使用 禁止cache */
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_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE;
MPU_InitStruct.IsShareable = MPU_ACCESS_NOT_SHAREABLE;
MPU_InitStruct.Number = MPU_REGION_NUMBER3;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL1;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.DisableExec = MPU_INSTRUCTION_ACCESS_DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
/* 配置FMC 片选3 的支持*/
MPU_InitStruct.Enable = MPU_REGION_ENABLE;
MPU_InitStruct.BaseAddress = 0x68000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_256B;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.IsBufferable = MPU_ACCESS_NOT_BUFFERABLE;
MPU_InitStruct.IsCacheable = MPU_ACCESS_NOT_CACHEABLE; // 此外设需配置为无cache,否则会重复片选和读写使能
MPU_InitStruct.IsShareable = MPU_ACCESS_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);
/*使能 MPU */
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}