1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

STM32F407のCCMRAMを使う

Posted at

背景

現在、USB-MIDIインターフェースをSTM32F407で作っています。
これは単なるUSB-MIDIへの変換だけでなく、MIDI to MIDIへのルーティングやイベントフィルタ、チャンネル・マッピング、そのほか多くの機能を提供するものに仕上げる予定です。

これらの機能を実現するため、

  • MIDIで受信したデータを保持
  • 受信データの演算
  • 送信先への振り分け

などの処理を行いますが、これを行うには一時的なメモリがKバイトオーダーで必要になります。
今まで何も考えなければ、必要時スタックに演算用の領域を確保し、終わったら解放...mallocを使わなければこんな感じ。
mallocは多用すると使用可能領域が断片化するかもしれないので、基本的には使わないようにしています。

スタックを使えば特に問題ないですが、STM32F407にはCCMRAMという64Kバイトのメモリが0x1000_0000番地に配置されています。
普通にソフトを書いていると、ここは使われずに放置されています。

であれば、演算用のメモリとして使えば、内蔵SRAMをもっと他のことに使える!
ということで、CCMRAMを使うことを考えます。

STM32F407のメモリマップ

STM32CubeMXで生成されるリンカマップを見てみると、以下のようになっています。

STM32F407VGTX_FLASH.ld
MEMORY
{
  CCMRAM (xrw) : ORIGIN = 0x10000000,   LENGTH = 64K
  RAM    (xrw) : ORIGIN = 0x20000000,   LENGTH = 128K
  FLASH   (rx) : ORIGIN = 0x08000000,   LENGTH = 1024K
}

内蔵SRAMが128Kバイト、CCMRAMが64Kバイトあります。
普通にソフトを書いていると、RAMの128Kバイトに変数などが割り当てられ、CCMRAMは使われません。

CCMRAMを使う方法

変数に直接アドレスを指定する

CCMRAMが0x1000_0000番地から割り当てられていることはわかっています。
だったら、直接アドレスを指定して使うことができます。

*((unsigned char *)0x10000000) = 0x55;
*((unsigned short *)0x10000010) = 0x55aa;

#define PATTERN_ADDR  0x1000000040

*((char *)PATTERN_ADDR) = 0x11;

これが一番簡単です。
でも、アドレスの管理を自分でしなくてはいけませんね。
これは大変ですが、変数が少なければこれでもいいかも。
汎用性はないですが、とにかく簡単です。

変数を構造体にまとめて構造体をアドレス指定

直接アドレスを指定するのは変わりませんが、CCMRAMで使いたい変数などを構造体にまとめてしまいます。

typedef struct CCMRAM_VARS {
    unsigned char leftData;
    unsigned short rightArign;
    char onPattern[16];
}CCMRAM_VARS;

((CCMRAM_VARS *)0x10000000)->leftData = 0x55;

CCMRAMのアドレスを指定するのは同じですが、変数のアドレス管理はしなくて良くなります。
常に構造体メンバの名前でアクセスできます。
大きなシステムではよく使っていました。
複数のCPUが並列動作するシステムでは、丸ごとコピーを渡したりするのに便利だし、メモリの管理もコンパイラに任せられます。

CCMRAMのセクションを作ってリンカに任せる

この方法は一番柔軟性があります。
通常通り変数を生成して、それをRAMではなくCCMRAMのセクションに割り当てればいいだけです。
CCMRAMへのアドレス割り当てはリンカがやってくれます。
初期値付きの変数もCCMRAMへ配置できます。
この方法を利用するには、

  • リンカスクリプト
  • スタートアッププログラム(アセンブラ)
  • 変数宣言時にセクションを指定する

という手続きが必要です。
自分の備忘録として、この方法を解説します。

リンカスクリプトの修正

調べるまで気づかなかったのですが、実はCCMRAMを使うための下記記述がリンカスクリプトに記述されていました。

STM32F407VGTX_FLASH.ld
  _siccmram = LOADADDR(.ccmram);

  /* CCM-RAM section
  *
  * IMPORTANT NOTE!
  * If initialized variables will be placed in this section,
  * the startup code needs to be modified to copy the init-values.
  */
  .ccmram :
  {
    . = ALIGN(4);
    _sccmram = .;       /* create a global symbol at ccmram start */
    *(.ccmram)
    *(.ccmram*)

    . = ALIGN(4);
    _eccmram = .;       /* create a global symbol at ccmram end */
  } >CCMRAM AT> FLASH

ccmramセクションの構造を見ると、初期値付き変数のセクションです。
初期値の記述先(ロードアドレス)を_siccmram = LOADADDR(.ccmram)で求めています。
じゃ、ccmramセクションに配置すればすぐ使えるのか...と思うのですが、IMPORTANT NOTE!を見ると、このセクションは初期化されないので、スタートアップコードを修正しなさい...と書かれてます。

ということで、初期値付き変数セクションはこのままccmramセクションを利用します。

初期値ゼロの変数セクションを作る

いわゆるbssと同じセクションをCCMRAM上に配置します。
セクション名はbssに似せてbbbにしました。

STM32F407VGTX_FLASH.ldに追加する
  /**** 初期値0のCCMRAM変数セクションを追加 ****/
  . = ALIGN(4);
  .bbb :
  {
    _sbbb = .;         /* define a global symbol at bbb start */
    *(.bbb)
    *(.bbb*)

    . = ALIGN(4);
    _ebbb = .;         /* define a global symbol at bbb end */
  } >CCMRAM

スタートアップのプログラムでゼロフィルするために、_sbbbと_ebbbというグローバルシンボルを用意します。
bbbセクションのスタートとエンドのアドレスです。
この範囲をスタートアッププログラムでゼロフィルします。

スタートアッププログラムを修正する

スタートアッププログラムはアセンブラで記述されています。
主にRAMのゼロフィル、初期値のロードが行われています。
ここに、新しく作ったccmram、bbbセクションの初期化を追記します。

初期値付き変数のccmramセクションを初期化する

まずは通常の初期値付き変数(dataセクション)の初期化を見てみます。
dataセクションの

  • スタートアドレスsdata
  • エンドアドレスedadta
  • 初期値の配置アドレスsidata

を使って、内蔵Flashから内蔵SRAMへ初期値をコピーしています。
リンカスクリプトでアラインメントを4にしているので、値のコピーは4バイトまとめてやってますね。

startup_stm32f407vgtx.s、初期値付き変数の初期化
/* Copy the data segment initializers from flash to SRAM */  
  ldr r0, =_sdata
  ldr r1, =_edata
  ldr r2, =_sidata
  movs r3, #0
  b LoopCopyDataInit

CopyDataInit:
  ldr r4, [r2, r3]
  str r4, [r0, r3]
  adds r3, r3, #4

LoopCopyDataInit:
  adds r4, r0, r3
  cmp r4, r1
  bcc CopyDataInit

これをそのまま使わせてもらいましょう。
分岐のためのラベルに適切な名前を付けます。

startup_stm32f407vgtx.sへ追加する
/**** Copy the data segment initializers from flash to CCMRAM ****/
  ldr r0, =_sccmram
  ldr r1, =_eccmram
  ldr r2, =_siccmram
  movs r3, #0
  b LoopCopyccmramInit

CopyccmramInit:
  ldr r4, [r2, r3]
  str r4, [r0, r3]
  adds r3, r3, #4

LoopCopyccmramInit:
  adds r4, r0, r3
  cmp r4, r1
  bcc CopyccmramInit

初期値0変数のbbbセクションを初期化する

同じくbssセクションのゼロフィル処理を見てみましょう。

startup_stm32f407vgtx.s、初期値0変数の初期化
/* Zero fill the bss segment. */
  ldr r2, =_sbss
  ldr r4, =_ebss
  movs r3, #0
  b LoopFillZerobss

FillZerobss:
  str  r3, [r2]
  adds r2, r2, #4

LoopFillZerobss:
  cmp r2, r4
  bcc FillZerobss
  • スタートアドレスsbss
  • エンドアドレスebss

を使って、指定アドレスを0で埋めています。
dataセクションと同じく、リンカスクリプトでアラインメントを4にしているので、4バイトまとめて0を入れてます。

同じように、これをそのまま使わせてもらいましょう。
ccmramセクション同様、分岐ラベルは適切な名前を付けます。

startup_stm32f407vgtx.sへ追加する
/**** Zero fill the CCMRAM segment. ****/
  ldr r2, =_sbbb
  ldr r4, =_ebbb
  movs r3, #0
  b LoopFillZeroccmram

FillZeroccmram:
  str  r3, [r2]
  adds r2, r2, #4

LoopFillZeroccmram:
  cmp r2, r4
  bcc FillZeroccmram

これでCCMRAMもSRAMと同じように扱えます。

CのソースコードでCCMRAMを使う

こんな感じで書けば使えます。

__attribute__((section(".bbb"))) PROCESSING_DATA processingData;
__attribute__((section(".ccmram"))) unsigned char demoData[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};

動作を見てみる

ちゃんとゼロフィル、初期値ロードがされていますね。

スクリーンショット 2025-06-27 10.41.32.png

最後に

今までそこそこSRAMが足りていたので気にしてませんでしたが、64Kバイトはデカい!
アプリケーションによっては使ったほうがいい時もあります。
CCMRAMを検討している方の参考になれば幸いです。

あと、この手法はChatGPTでも紹介してくれますよ♪

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?