Instant Engineering

エンジニアの仕事効率を上げる知識をシェアするWeb記事/機械設計/TPS/QC品質管理

メモリマップとは?I/Oとファイルで解説

「同じアドレスなのに、ある場所を読むとRAMの値が返り、別の場所を読むとスイッチの状態が返る」

組込み開発を学び始めると、このような説明に戸惑うことがあります。

パソコンの感覚では、メモリはデータを保存する場所に見えます。しかしマイコンでは、GPIO、タイマ、UART、ADCなどの周辺機能も、メモリと同じようにアドレスで扱われます。

メモリマップとは、CPUから見たアドレス空間に、ROM、RAM、周辺レジスタ、外部メモリなどをどの範囲に配置するかを示した対応表です。

本記事では、メモリマップの基本、メモリマップドI/O、I/Oポート方式との違い、リンカスクリプトやファイルのメモリマップとの違いまで、組込み開発の実務目線で解説します。

 

1. メモリマップとは何か

メモリマップとは、CPUがアクセスできるアドレス空間に、どの機能や記憶領域が割り当てられているかを示す地図のようなものです。

住所地図で「この番地は工場、この番地は倉庫、この番地は事務所」と決まっているのと同じように、マイコン内部でもアドレスごとに役割が決まっています。

たとえば、あるマイコンでは  0x00000000 付近にフラッシュメモリ、 0x20000000 付近にRAM、 0x40000000 付近に周辺レジスタが配置されます。

CPUは「これはRAMだから特別な命令で読む」「これはGPIOだから別の命令で読む」と考えているわけではありません。基本的には、指定されたアドレスへ読み書きしているだけです。

そのアドレスの先に何がつながっているかを整理したものが、メモリマップです。

組込み開発では、メモリマップを理解していないと、レジスタ設定、起動処理、リンカ設定、割り込みベクタ配置、メモリ使用量の見積もりが曖昧になります。

 

2. メモリマップが組込み開発で重要な理由

メモリマップは、単なるデータシート上の表ではありません。

ファームウェアがどこから起動し、変数がどこに置かれ、周辺機能をどのアドレスで操作するかを決める、組込みソフトウェアの土台です。

CPUはアドレスを指定して動く

CPUが命令を読むときも、変数にアクセスするときも、周辺レジスタを操作するときも、最終的にはアドレスを指定します。

そのため、プログラム上で何気なく使っている変数やレジスタ名も、実際にはどこかのアドレスに対応しています。

配置を間違えると起動しない

組込み機器では、起動直後にCPUが読み出すベクタテーブルや初期化コードの配置が重要です。

フラッシュメモリの先頭に置くべき領域を別の場所に置いてしまうと、そもそもプログラムが起動しません。

周辺機能の操作にも直結する

GPIOの出力をONにする、タイマを開始する、UARTの送信バッファにデータを書く、といった処理は、対応する周辺レジスタへの読み書きで実現されます。

つまり、メモリマップは「ハードウェアをソフトウェアから操作するための住所録」でもあります。

 

3. メモリマップの基本構成

メモリマップに登場する領域は、マイコンやCPUによって異なります。

ただし、組込み開発でよく見る領域はおおむね共通しています。

領域 主な役割 実務での見方
フラッシュメモリ プログラムや固定データを保存する 電源を切っても消えないため、実行コードや定数テーブルを置きます。
RAM 変数、スタック、ヒープを置く 実行中に変化するデータを置きます。容量不足は暴走や異常終了につながります。
周辺レジスタ GPIO、タイマ、UARTなどを制御する メモリマップドI/Oとして、特定アドレスへの読み書きで周辺機能を操作します。
外部メモリ 外付けSRAM、SDRAM、NOR Flashなど 表示用バッファ、大容量データ、外部コード領域として使う場合があります。
予約領域 未使用または将来用途の領域 アクセスするとバスエラーやハードフォールトになることがあります。

重要なのは、メモリマップが「容量一覧」ではなく「アドレスと役割の対応表」である点です。

同じRAM容量でも、どのアドレスからどのアドレスまで使えるかがわからなければ、リンカ設定やデバッグでは役に立ちません。

 

4. メモリマップドI/Oとは

メモリマップドI/Oとは、GPIOやタイマなどの周辺機能を、メモリと同じアドレス空間に割り当てて操作する方式です。

英語では Memory Mapped I/O と呼ばれ、略してMMIOと表記されることもあります。

たとえば、あるGPIOの出力レジスタが  0x40020014 に割り当てられている場合、そのアドレスへ値を書き込むことでピンの出力状態を変更できます。

ソフトウェアから見ると、メモリへ値を書いているように見えます。

しかし実際には、そのアドレスの先にはRAMではなく、ハードウェアの制御回路がつながっています。

レジスタ名はアドレスの別名

マイコンの開発では、GPIOA_ODR、USART_DR、TIM_CR1のようなレジスタ名を使います。

これらは人間が読みやすい名前ですが、実体は特定アドレスに割り当てられたレジスタです。

読み書きが副作用を持つ

通常のRAMは、読み書きしてもデータが変わるだけです。

一方、周辺レジスタでは、書き込むとモータが動く、通信が始まる、割り込みフラグがクリアされる、といった副作用が起きます。

この副作用を理解しないままデバッグで安易にレジスタを操作すると、意図しない動作を招くことがあります。

 

5. メモリマップドI/OとI/Oポート方式の違い

I/O制御には、大きく分けてメモリマップドI/OとI/Oポート方式があります。

どちらもCPUが周辺機能を操作するための仕組みですが、アドレス空間の考え方が異なります。

比較項目 メモリマップドI/O I/Oポート方式
アドレス空間 メモリと周辺機能を同じ空間に配置する メモリ空間とは別にI/O空間を持つ
アクセス命令 通常のロード・ストア命令で扱いやすい 専用のI/O命令を使う場合があります
組込みでの利用 多くのマイコンで一般的 一部のCPUアーキテクチャで使われます
設計上の特徴 プログラムから自然に扱える メモリ空間を圧迫しにくい場合があります

組込みマイコンでは、メモリマップドI/Oが広く使われます。

そのため、GPIOやUARTを制御するときには、専用命令を意識するよりも「特定アドレスのレジスタを読み書きしている」と考えるほうが理解しやすいです。

ただし、アーキテクチャによってはI/Oポート方式が登場します。古い資料やPC寄りの低レベル制御を読むときは、この違いを知っておくと混乱を避けられます。

 

6. メモリマップとリンカスクリプトの関係

メモリマップを実際のプログラム配置に反映する役割を持つのが、リンカスクリプトです。

C言語で書いたソースコードは、コンパイルされただけでは、最終的にどのアドレスへ置かれるかが決まりません。

リンカが、コード、定数、初期化済み変数、未初期化変数、スタックなどを、指定されたメモリ領域へ配置します。

代表的なセクション

セクション 主な内容 配置される場所の例
.text 実行コード フラッシュメモリ
.rodata 読み取り専用の定数 フラッシュメモリ
.data 初期値を持つグローバル変数 実行時はRAM、初期値はフラッシュに保持
.bss 初期値なし、またはゼロ初期化の変数 RAM
stack 関数呼び出しや局所変数の作業領域 RAMの末尾付近に置かれることが多い
heap 動的メモリ確保の領域 RAM内に必要に応じて確保

組込み開発では、リンカスクリプトの設定ミスが原因で、プログラムが起動しない、変数が壊れる、スタックが別領域を破壊する、といった不具合が起きます。

メモリマップを読む力は、リンカスクリプトを正しく理解するためにも必要です。

 

7. 起動処理で見るメモリマップの使われ方

メモリマップは、プログラム起動時にも重要な役割を持ちます。

特に組込みシステムでは、電源投入直後からmain関数に到達するまでに、複数の初期化処理が行われます。

起動直後に読む領域

多くのマイコンでは、リセット直後に決められたアドレスから初期スタックポインタやリセットハンドラのアドレスを読み出します。

この情報が置かれる領域を、ベクタテーブルと呼びます。

data領域の初期化

 .data セクションの変数は、実行時にはRAM上に存在します。

しかし、電源投入直後のRAMには期待した初期値が入っていないため、起動処理でフラッシュ上の初期値をRAMへコピーします。

bss領域のゼロクリア

 .bss セクションは、起動時にゼロで初期化される領域です。

このゼロクリアが正しく行われないと、初期値が0であるはずの変数に不定値が入り、再現性の低い不具合につながります。

このように、メモリマップは「どこに何を置くか」だけでなく、「起動時に何をどこからどこへ移すか」にも関係します。

 

8. ファイルのメモリマップとの違い

メモリマップという言葉は、組込み開発だけで使われるわけではありません。

OSやアプリケーション開発では、ファイルをメモリ空間に対応付ける意味で「メモリマップファイル」という言葉が使われます。

これは、ファイルの内容を通常の読み書き関数で扱うのではなく、メモリ上の配列のようにアクセスする仕組みです。

大きなファイルを効率よく扱う場合や、複数プロセスでデータを共有する場合に使われます。

用語 意味 主な利用場面
組込みのメモリマップ アドレス空間にROM、RAM、周辺レジスタを配置した対応表 マイコン、ファームウェア、リンカ設定
メモリマップドI/O 周辺機能をメモリと同じアドレス空間で操作する方式 GPIO、UART、タイマ、ADC制御
メモリマップファイル ファイルを仮想メモリ空間に対応付ける仕組み OS、アプリケーション、大容量ファイル処理

同じ「メモリマップ」という言葉でも、組込みのアドレス配置と、OS上のファイルマッピングは目的が異なります。

検索時に混同しやすいので、「マイコンのメモリマップ」なのか「ファイルのメモリマップ」なのかを切り分けることが大切です。

 

9. メモリマップを見るときの実務ポイント

データシートのメモリマップ表は、最初は数字の羅列に見えます。

しかし、見るべきポイントを決めておくと、実務でかなり使いやすくなります。

まずROMとRAMの範囲を見る

最初に確認するのは、プログラムを置くフラッシュメモリと、実行時データを置くRAMの開始アドレスとサイズです。

これがリンカスクリプトやメモリ使用量の基準になります。

周辺レジスタのベースアドレスを見る

次に、GPIO、UART、SPI、I2C、タイマなどのベースアドレスを確認します。

各レジスタは、ベースアドレスにオフセットを加えた位置に並んでいることが多いです。

アクセス禁止領域を避ける

予約領域や未実装領域へアクセスすると、バスエラー、ハードフォールト、予期しない停止が起きる場合があります。

ポインタの値が壊れているときに、こうした領域へアクセスして異常停止することがあります。

キャッシュやアライメントも確認する

高性能な組込みプロセッサでは、キャッシュ、MPU、MMU、アライメント制約も関係します。

単にアドレスが存在するだけでなく、どの属性でアクセスすべきかまで確認することが重要です。

 

10. メモリマップ関連で起きやすいトラブル

メモリマップに関する不具合は、コンパイルエラーではなく実機上の異常として現れることが多いです。

そのため、原因切り分けにはメモリ配置の視点が欠かせません。

トラブル 主な原因 確認ポイント
起動しない ベクタテーブルや起動コードの配置ミス リセットベクタ、ブート設定、リンカスクリプトを確認します。
変数が壊れる スタックオーバーフロー、RAM領域の重複 mapファイル、スタックサイズ、ヒープサイズを確認します。
レジスタ操作が効かない ベースアドレス違い、クロック未供給、ビット設定ミス データシートのアドレス表と初期化順序を確認します。
ハードフォールトが発生する 未実装領域へのアクセス、不正ポインタ 例外発生時のPC、アクセスアドレス、スタック内容を確認します。
デバッグ時だけ動く 初期化漏れ、メモリ内容への依存  .data コピー、 .bss ゼロクリア、未初期化変数を確認します。

特に注意したいのは、mapファイルを見ずにRAM不足や配置ミスを推測だけで判断してしまうことです。

実行ファイルのリンク結果を確認すれば、各セクションがどのアドレスに置かれているか、どれだけ容量を使っているかを確認できます。

 

11. メモリマップを理解するための読み方の順番

メモリマップは、いきなりすべてを理解しようとすると難しく感じます。

実務で使うなら、次の順番で読むと整理しやすくなります。

Step 1:CPUのアドレス空間を確認する

まず、CPUが扱えるアドレス範囲を確認します。

32bit CPUであれば、理論上は  0x00000000 から  0xFFFFFFFF までの空間を扱えますが、実際にすべてが実装されているわけではありません。

Step 2:ROMとRAMの開始アドレスを確認する

次に、フラッシュメモリとRAMの開始アドレス、容量、終端アドレスを確認します。

ここが、リンカスクリプトとメモリ使用量の基本情報になります。

Step 3:周辺レジスタ領域を確認する

GPIO、UART、SPI、I2C、タイマ、ADCなど、使う周辺機能のベースアドレスを確認します。

実際の開発では、使用する周辺機能だけをまず追えば十分です。

Step 4:mapファイルと照合する

ビルド後に生成されるmapファイルを確認し、 .text .data .bss、stack、heapが想定通りの領域に入っているかを確認します。

メモリ使用量が限界に近い場合は、どの関数や変数が容量を使っているかまで追う必要があります。

 

12. よくある質問

Q1. メモリマップとメモリ容量は同じ意味ですか?

同じではありません。

メモリ容量は「どれだけ入るか」を示す値で、メモリマップは「どのアドレス範囲に何が配置されているか」を示す対応表です。

Q2. メモリマップドI/Oは普通のメモリと同じように扱えますか?

プログラム上は同じようにアドレスで読み書きできますが、意味は大きく異なります。

周辺レジスタへの書き込みは、通信開始、出力ON、割り込みフラグ解除などの副作用を持つため、通常のRAMとは区別して考える必要があります。

Q3. mapファイルとメモリマップは違いますか?

違います。

メモリマップはハードウェア側のアドレス配置を示す資料で、mapファイルはビルド後に各関数や変数がどこへ配置されたかを示すリンク結果です。

Q4. ファイルのメモリマップと組込みのメモリマップは関係ありますか?

名前は似ていますが、目的は異なります。

組込みのメモリマップはハードウェアのアドレス配置、メモリマップファイルはOS上でファイルを仮想メモリに対応付ける仕組みです。

Q5. メモリマップを理解するには何から学べばよいですか?

まずは、ROM、RAM、周辺レジスタ、リンカスクリプト、mapファイルの関係から学ぶのがよいです。

GPIOのレジスタを実際に読み書きする小さなプログラムを追うと、メモリマップドI/Oの意味がつかみやすくなります。

 

13. まとめ

メモリマップとは、CPUのアドレス空間にROM、RAM、周辺レジスタ、外部メモリなどをどのように配置するかを示した対応表です。

組込み開発では、プログラムの配置、起動処理、レジスタ操作、RAM使用量の確認、トラブル解析に直結します。

特に重要なのは、メモリマップドI/Oの考え方です。

GPIOやUARTなどの周辺機能は、特定アドレスのレジスタとして配置され、ソフトウェアからの読み書きで制御されます。

一方で、通常のRAMとは異なり、周辺レジスタへの読み書きには副作用があります。

また、ファイルのメモリマップとは意味が異なるため、組込みのアドレス配置とOS上のファイルマッピングを混同しないことも大切です。

メモリマップを読めるようになると、リンカスクリプト、mapファイル、起動処理、周辺レジスタの理解が一気につながります。

マイコンやファームウェアの動作を「なんとなく」ではなく、アドレスと配置の根拠から説明できるようになることが、組込み開発の大きな一歩です。