Arduinoメモリガイド

この記事では、Arduino®ボードに内蔵されているメモリブロックを学びます。


AUTHOR: Arduino, José Bagur, Taddy Chung、LAST REVISION: 2022/09/23 17:14


マイクロコントローラユニット(MicroController Unit: MCU)は、典型的には特定のアプリケーションやタスクを実行する集積回路(IC: Integrated Circuit)です。通常この型のICは、環境から情報やデータを収集し、データを処理し、収集したデータに従った特定の出力を生成します。今日、マイクロコントローラはどこにでもあります。現在の組込みシステムの必要不可欠な部品であり、スマートウォッチから電気自動車まで、実質的に我々の世界のどこにでもあります。また、今では火星の表面にも存在します。

マイクロコントローラに必要不可欠な部品の一つにメモリがあります。メモリはマイクロコントローラに情報を一時的あるいは恒久的に格納し、さまざまな目的に使われます。この記事では、マイクロコントローラのメモリ構成を、Arduino®ボードに搭載されているものに着目し、見ていきます。Arduinoベースのシステムで、メモリ使用量の管理や測定方法、最適化方法も説明します。

メモリとは何か?

メモリブロックは、現在の組込みシステム、特に、マイクロコントローラベースのシステムでは、必要不可欠な部品です。メモリブロックは、情報やデータを格納したり取り出したりする半導体素子です。マイクロコントローラのCPU(Central Processing Unit)は、特定のタスクを実行するために、メモリブロックに格納されたデータを使い、処理します。

以下の図で示すように、マイクロコントローラのメモリブロックは通常配列として表現されます。メモリの配列は、データを格納できるセルに分割され、メモリ配列のアドレスや相対位置を表す一意の識別子によってアクセスされます。メモリセル内の情報は2進数(ビット)を使って格納され、通常バイト(8ビット)を構成します。また、後でMCUやマイクロコントローラベースシステムの他のコンポーネントが取り出すことができます。

計算システムのメモリには、揮発性のものと不揮発性のものがあります。揮発性メモリは一時的なメモリで、システムが動作しているときにデータが格納されますが、システムの電源が切れると永遠に失われるということを意味します。不揮発性メモリは永続メモリで、システムの電源が切れてもデータは失われません。

メモリアーキテクチャ入門

コンピュータアーキテクチャは広大なトピックです。Arduino®ボードで使われるマイクロコントローラで、メモリがどのように構成されているのかを理解できるように、全体像を説明します。

計算機の初期に、フォンノイマン型アーキテクチャとハーバードアーキテクチャという2つのアーキテクチャ、もしくは、計算システム内の構成が現れました。

フォンノイマン型アーキテクチャ

数学者であり、物理学者・コンピュータサイエンティストでもあるJohn von Neumannにちなみ名付けられた、フォンノイマン型アーキテクチャは、1940年半ばに導入されました。プリンストンアーキテクチャとしても知られています。このアーキテクチャは、プログラムデータと命令を同じメモリユニットに格納します。

フォンノイマン型アーキテクチャ

フォンノイマン型アーキテクチャ

上図に示すように、データも命令も同じ通信バスを使ってCPUはアクセスします。ほとんどすべてのデジタルコンピュータデザインがこのアーキテクチャに基づいているので、フォンノイマンアーキテクチャは基礎となっています。

ハーバードアーキテクチャ

Harvard Mark Iというリレーベースのコンピュータにちなんで名づけられたハーバードアーキテクチャは、1940年代半ばに導入されました。子のアーキテクチャのおもな特徴は、2つの分離したメモリユニットを使うことです。ひとつにはプログラム命令を格納し、もう一つにはプログラムデータを格納します。ハーバードアーキテクチャでは、双方のメモリは異なる通信バスを使ってCPUはアクセスします。

ハーバードアーキテクチャ

ハーバードアーキテクチャ

現代のアーキテクチャ: ハイブリッド

現代の計算システムはハイブリッドアーキテクチャを使っています。これは、フォンノイマン型モデルとハーバードモデルの両方のいいところを活用し、性能を最大化するものです。

マイクロコントローラは、通常、組込みアプリケーションで利用されます。定義されたタスクを、少ない、もしくは、制限されたたリソースを使うだけで、確実かつ高効率に実行する必要があります。これが、ハーバードアーキテクチャが主に使われる理由です。マイクロコントローラは、同時にアクセスされる必要のある小さいプログラムとデータメモリを持っています。しかし、ハーバードアーキテクチャは、マイクロコントローラでいつも使われるわけではありません。ハイブリッドもしくはフォンノイマン型アーキテクチャモデルを使うマイクロコントローラファミリーもあります。

Arduino®ボードのアーキテクチャ

Arduino®ボードは主に、AVR®かAARM®の2つのマイクロコントローラファミリーに基づいています。AVR®ファミリーのマイクロコントローラはハーバードアーキテクチャに基づいていますが、AARM®ファミリーのマイクロコントローラは、フォンノイマンアーキテクチャかハーバードアーキテクチャに基づいています。以下の表はArduinoボードのマイクロコントローラアーキテクチャの概要です。

ボード マイクロコントローラ ファミリー アーキテクチャ
UNO Mini ATmega328P AVR Harvard
UNO Rev3 ATmega328P AVR Harvard
UNO WiFi Rev2 ATmega4809 AVR Harvard
UNO Rev3 SMD ATmega328P AVR Harvard
Leonardo ATmega32u4 AVR Harvard
Mega 2560 Rev3 ATmega2560 AVR Harvard
Micro ATmega32u4 AVR Harvard
Zero ATSAMD21G18 ARM Cortex M0+ Von Neumann
Portenta H7 STM32H747 ARM Cortex M4/M7 Harvard
Nicla Sense ME nRF52832 ARM Cortex M4 Harvard
Nano RP2040 Connect RP2040 ARM Cortex M0+ Von Neumann
MKR FOX 1200 ATSAMD21G18 ARM Cortex M0+ Von Neumann
MKR NB 1500 ATSAMD21G18 ARM Cortex M0+ Von Neumann
MKR Vidor 4000 ATSAMD21G18 ARM Cortex M0+ Von Neumann
MKR WiFi 1010 ATSAMD21G18 ARM Cortex M0+ Von Neumann
MKR Zero ATSAMD21G18 ARM Cortex M0+ Von Neumann
MKR1000 WIFI ATSAMW25H18 ARM Cortex M0+ Von Neumann
MKR WAN 1300 ATSAMD21G18 ARM Cortex M0+ Von Neumann
MKR WAN 1310 ATSAMD21G18 ARM Cortex M0+ Von Neumann
Nano ATmega328P AVR Harvard
Nano Every ATmega4809 AVR Harvard
Nano 33 IoT ATSAMD21G18 ARM Cortex M0+ Von Neumann
Nano 33 BLE nRF52840 ARM Cortex M4 Harvard
Nano 33 BLE Sense nRF52840 ARM Cortex M4 Harvard

メモリの種類

マイクロコントローラ内の全ての異なるメモリユニットは、RAMとROMの2つの種類に分けることができます。マイクロコントローラベースシステムのRAM(Random Access Memory)は揮発性のメモリで、システムのファームウェアの変数などの一時データの格納に用いられます。マイクロコントローラベースシステムのROM(Read Only Memory)は不揮発性のメモリで、システムファームウェアのような永続データの格納に用いられます。

マイクロコントローラベースシステムのRAMとROMは大きく3種類に分類することができます。

  • フラッシュ
  • RAM
  • EEPROM

フラッシュ

マイクロコントローラベースシステムのフラッシュメモリは、ROMの一種です。フラッシュメモリは、実行されるシステムのファームウェアを格納する場所です。有名なBlink.inoスケッチを例にすると考えてみます。このスケッチをコンパイルすると、後でArduinoボードのフラッシュメモリに格納されることになるバイナリファイルが生成されます。スケッチはその後、ボードに電源が入ると実行されます。

RAM

マイクロコントローラベースシステムのRAMは、システムの一時データや実行時データを格納する場所です。例えば、プログラムの関数によって生成される変数などです。マイクロコントローラ内のRAMは通常SRAMです。この型のRAMは、1ビットのデータを格納するのに一つのフリップフロップを使います。マイクロコントローラでみられるもう一つのRAMの型にはDRAMがあります。

EEPROM

マイクロコントローラベースシステムのEEPROM(Erasable Programmable Read-Only Memory)は、ROMの一種です。実際、フラッシュメモリはEEPROMの一種です。フラッシュメモリとEEPROMの大きな違いは、管理方法です。EEPROMはバイトレベルで監理(書き込み・消去)できますが、フラッシュはブロックレベルで管理されます。

Arduino®ボードのメモリ割り当て

前述の通り、Arduino®ボードは主に、AVR®かAARM®の2つのマイクロコントローラファミリーに基づいています。双方の秋てクチャでメモリ割り当て方法に違いがあることを理解するのが重要です。ハーバードベースのAVRアーキテクチャでは、メモリは以下の図のように構成されています。

AVRのメモリマップ

AVRのメモリマップ

AVRベースのArduinoボードで重要なのは、SRAMが異なるセクションに分けられていることです。

  • テキスト(Text)
  • データ(Data)
  • BSS
  • スタック(Stack)
  • ヒープ(Heap)

テキストセクションは、フラッシュメモリにロードされた命令を含んでいます。データセクションは、スケッチ内で初期化された変数を含みます。BSSセクションは、初期化されていないデータを含みます。スタックセクションは、関数や割り込みのデータを格納します。ヒープセクションは実行時に生成された変数を格納します。

ハイブリッドARMアーキテクチャでは、いわゆるメモリマップが実装され、システムオンチップ(System On a Chip: SoC)の外付けDRAMのアドレス空間の要求により、32ビットあるいは36ビット、40ビットの異なるアドレスマップになります。メモリマップはSoC設計とのインターフェイスを提供し、高レベルコーディングでほとんどのシステム制御を行うことができます。割り込みモジュールと内蔵周辺機器を管理するため、メモリアクセス命令は高レベル命令で利用できます。これらの全てはメモリ管理ユニット(Memory Management Unit: MMU)によって制御されます。

メモリリソースはMMUにより扱われます。MMUの主な役割は、プロセッサが複数のタスクを、それぞれの仮想メモリ空間で独立して実行できるようにすることです。MMUは仮想メモリアドレスと物理メモリアドレス間の橋渡しをするために、変換テーブルを使います。仮想アドレスはソフトウェアのメモリ命令で監理されます。物理アドレスは、仮想アドレスから与えられる変換テーブルの入力に依存して制御される目盛システムです。

ARMベースのマイクロコントローラの、仮想および物理メモリの構成例を以下の図に示します。

ARMベースのマイクロコントローラのメモリ構成

ARMベースのマイクロコントローラのメモリ構成

ARMベースのマイクロコントローラのメモリは、前述したアドレス形式内で、以下のセクションから構成されます。

  • 仮想アドレス
    • カーネルコードとデータ
    • アプリケーションコードとデータ
  • 物理アドレス
    • ROM
    • RAM
    • フラッシュ(Flash)
    • 周辺機器(Peripherals)

以下の表に、特定のArduino®ボードのメモリ割り当てをまとめます。

ボード マイクロコントローラ ファミリー アーキテクチャ フラッシュ SRAM EEPROM
UNO Mini ATmega328P AVR Harvard 32kB 2kB 1kB
UNO Rev3 ATmega328P AVR Harvard 32kB 2kB 1kB
UNO WiFi Rev2 ATmega4809 AVR Harvard 48kB 6kB 256B
UNO Rev3 SMD ATmega328P AVR Harvard 32kB 2kB 1kB
Leonardo ATmega32u4 AVR Harvard 32kB 2.5kB 1kB
Mega 2560 Rev3 ATmega2560 AVR Harvard 256kB 8kB 4kB
Micro ATmega32u4 AVR Harvard 32kB 2.5kB 1kB
Zero ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
Portenta H7 (basic configuration) STM32H747 ARM Cortex M4/M7 Harvard 16MB 8MB -
Nicla Sense ME nRF52832 ARM Cortex M4 Harvard 512kB 64kB -
Nano RP2040 Connect RP2040 ARM Cortex M0+ Von Neumann - 264kB -
MKR FOX 1200 ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
MKR NB 1500 ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
MKR Vidor 4000 ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
MKR WiFi 1010 ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
MKR Zero ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
MKR1000 WIFI ATSAMW25H18 ARM Cortex M0+ Von Neumann 256kB 32kB -
MKR WAN 1300 ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
MKR WAN 1310 ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
Nano ATmega328P AVR Harvard 32kB 2kB 1kB
Nano Every ATmega4809 AVR Harvard 48kB 6kB 256B
Nano 33 IoT ATSAMD21G18 ARM Cortex M0+ Von Neumann 256kB 32kB -
Nano 33 BLE nRF52840 ARM Cortex M4 Harvard 1MB 256kB -
Nano 33 BLE Sense nRF52840 ARM Cortex M4 Harvard 1MB 256kB -
i
ProハードウェアのSDRAMとフラッシュメモリの容量は高度にカスタマイズ可能です。詳細はProのサイトを確認してください。

Arduino®ボードのメモリ使用量を測定する

メモリ使用量統計は、設計したコード構造によって影響のあるリソース管理に対する見通しを理解するのに役立ちます。メモリの負荷要件は、コードがどの程度効率的に設計されたかを見通すためのひとつの統計情報です。マイクロコントローラベースのシステムの資源は有限なので、重要な開発時考慮する要素の一つになります。ソフトウェアは問題や課題を避けるため、常に最大負荷容量に達することなく実行される必要があります。メモリ負荷は、特定のタスクが自由に利用できるRAMやフラッシュストレージに要求される空き容量として観測されます。

i
実行時の問題を避けるために、マイクロコントローラベースのシステムは常に最大メモリ容量を使い切ることなく実行される必要があります。

Arduino®ボードでのメモリ使用量管理方法を説明していきます。

フラッシュメモリの測定

Arduino®ボードのフラッシュメモリは、Arduino IDEを使って測定できます。前述したように、フラッシュメモリはアプリケーションのコードが格納される場所です。Arduino IDEは、コンパイラの出力コンソールを通じて、フラッシュメモリ資源の資料量を、開発者に知らせます。

例えば、AVRベースのArduino®ボード(Nano)の出力コンソールを以下に示します。

AVRベースのArduino®ボードのフラッシュメモリ測定

AVRベースのArduino®ボードのフラッシュメモリ測定

ARMベースのArduino®ボード(MKR WAN 1310)の出力コンソールを以下に示します。

ARMベースのArduino®ボードのフラッシュメモリ測定

ARMベースのArduino®ボードのフラッシュメモリ測定

別のARMベースのArduino®ボード(Portenta H7)の出力コンソールを以下に示します。

ARMベースのArduino®ボードのフラッシュメモリ測定

ARMベースのArduino®ボードのフラッシュメモリ測定

ボードがAVRベースなのか、ARMベースなのかによってコンパイラの出力が変わることに注意してください。

SRAMのメモリ測定

IDEにより、コードのコンパイルとアップロードに成功しても、突然止まってしまうことがあります。これらの問題は、メモリ資源の占有や割り当てるメモリの不足により発生する問題であることが多いです。これを解決するには、どのコード部分のメモリ要求が利用可能な資源量を超えているのかを理解する必要があります。以下のコード例はAVRベースのArduino®ボードでのSRAM使用量の測定に利用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void display_freeram() {
  Serial.print(F("- SRAM left: "));
  Serial.println(freeRam());
}

int freeRam() {
  extern int __heap_start,*__brkval;
  int v;
  return (int)&v - (__brkval == 0  
    ? (int)&__heap_start : (int) __brkval);  
}

ヒープセクションは、実行時に生成される変数が格納される場所だということを思い出してください。コード中の、__heap_start__brkvalは以下の通りです。

  • __heap_start: ヒープセクションの開始地点
  • __brkval: ヒープによって使われる最後のメモリアドレスへのポインタ

以下のコード例はARMベースのArduino®ボードでのSRAM使用量の測定に利用できます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
extern "C" char* sbrk(int incr);

void display_freeram(){
  Serial.print(F("- SRAM left: "));
  Serial.println(freeRam());
}

int freeRam() {
  char top;
  return &top - reinterpret_cast<char*>(sbrk(0));
}

上記のコードは、Michael P. FlagaによるArduino-MemoryFreeライブリのものです。

EEPROMのメモリ測定

EEPROMのメモリ管理はArduino IDEにより既にインストールされている固有のライブラリを使うことで容易に行うことができます。EEPROMライブラリは、EEPROMメモリの読み込み、書き込み、消去に利用できます。以下のコードは、write関数とread関数によって1バイトの情報がどのようにEEPROMに格納されるのかと、読み取られるのかを示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <EEPROM.h>

void setup() {
}

void loop {
  // Write data into an specific address of the EEPROM memory 
  EEPROM.write(address, value);

  // Read data of an specific address of the EEPROM memory 
  EEPROM.read(address);
}

以下のコードで示すように、全てのEEPROMメモリを0に設定することで、メモリをクリアすることもできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include <EEPROM.h>

void setup() {
}

void loop {
  for (int i = 0 ; i < EEPROM.length() ; i++) {
    // Clear EEPROM memory 
    EEPROM.write(i, 0);

}
i
EEPROMメモリの管理方法に関するさらなる情報は、このガイドを参照してください。

Arduinoベースシステムのメモリ利用を最適化する

システムがどのようにメモリ資源を利用するのかを知ることは、開発プロセスで最初に推奨される事項ですが、メモリ利用の最適化は全く異なります。開発という用語が示すように、コンポーネントが利用できないことなどによりデバイスの容量が削減されたりするなどの外部要因に依存し、要件が変更、調整されることがあります。このように、限られた少ないメモリ資源で実行できるように、コードアーキテクチャの最適化が必要になることがあります。

メモリ利用の最適化処理は、同じタスクの実行に要するメモリ資源量を削減するとともに、計算の複雑さの削減、タスクの処理に要する余分な時間の削減も意味します。メモリ利用の最適化処理は、より高度なアルゴリズムの開発が必要になりメモリ監理がより適切なものになるので、コード全体の最適化処理にもなります。

メモリ利用の最適化方法を説明していきます。

フラッシュメモリの最適化

フラッシュメモリの最適化は、最も直感的な最適化手法です。いくつかの詳細点を考慮することで、フラッシュメモリの容量である、コンパイルされたコードの容量を大幅に削減できます。

未使用のソースを削除する

新しいソースコードを削除することには、未使用のライブラリとコードの残骸を削除することが含まれます。コードの残骸には、メモリの不要な空間を占めている、もう利用していない関数や浮いた変数が含まれます。これはコンパイルされたコードの大きさを大幅に改善し、コンパイル処理をより明確にします。

処理のモジュール化

処理のモジュール化とは、異なるパラメータを受け取ることで繰り返し、もしくは、継続的に利用されるコードを包む関数を意味します。実装する追加タスクに要求されるメモリ量を削減しつつ、明確なコード構成と性能を維持できる素晴らしい方法です。

これによりコード構成は小さくなり、デバッグの際の理解がとても容易です。また、このためには、コード構成や特定のアルゴリズムを設計する際に開発者は、計算の複雑さを考慮する必要があります。

SRAMのメモリ最適化

マイクロコントローラベースのシステムで、SRAMメモリは恐らくもっとも重要なメモリユニットです。SRAMの使用量を最適化することは信頼性の高いマイクロコントローラベースのシステムを設計するための本質てきな事項です。SRAM不足は通常最も一般的に見られるメモリの問題です。SRAMの最適化はこの型の問題の削減に役立ちます。

行を表示するコマンドの理想的な使い方は、F()文字列ラッパを文字列に使うことです。以下の例を見てください。

文字列ラッパ

Serial.print()命令やSerial.println()命令は、SRAM空間を利用します。これは簡単ですが望ましくありません。理想的な方法は、Serial.print()命令やSerial.println()命令を、文字列を囲むF()文字列ラッパとともに使うことです。例えば、

1
Serial.println(F("Something"));

Somethingという文字列をF()ラッパでくるむと、文字列をフラッシュメモリに配置し、SRAM空間を利用しません。F()ラッパを使うと、SRAMからフラッシュメモリに配置換えするように見えます。SRAMと比べてフラッシュメモリは容量が大きいので、ヒープセクションを使うSRAMを使うよりも、フラッシュメモリを使う方が効率的です。これは、メモリ空間がいつも使えるということを意味しているわけではありません。フラッシュメモリも容量が限られているからです。Serial.print()命令やSerial.println()命令でコードを詰まらせることは推奨しません。コード内のもっとも重要な場所で使うようにしてください。

PROGMEM

文字列だけがSRAM空間を占有するわけではありません。グローバル変数もSRAM空間の非常に大きな容量を使います。グローバル変数やスタティック変数がSRAM空間に割り当てられると、ヒープメモリセクションをスタックのほうに押しやります。これらの変数がSRAM空間に割り当てられると、占有された空間は変更されません。これらの変数がより多く作成されると、より多くの空間を消費し、結果として、メモリ管理の不備によりシステムに問題や課題がおこります。

プログラムメモリを意味するPROGMEMは、先ほど説明したF()ラッパと同様、フラッシュメモリ空間に変数のデータを配置するのに使われます。しかし、PROGMEMの利用は、データの読み込み速度が遅いという欠点があります。RAMを使うとデータの読み込み速度はとても速いですが、PROGMEMはフラッシュメモリを使うので、同じデータサイズでは、RAMよりも遅くなります。このように、どの変数が重要で、どの変数がそうでないか、あるいは、優先度が低いかを理解し、設計することが重要です。

AVRベースのArduino®ボードでのPROGMEMの使い方を、以下のコード例で示します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <avr/pgmspace.h>

// Basic PROGMEM structure 
const PROGMEM DataType Variable_Name[] = {var0, var1, var2 ...};

// Storing an unsigned, 16-bit, integer
const PROGMEM uint16_t NumSet[] = {0, 1, 1, 2, 3, 5, 8 ...};

// Storing a char in PROGMEM
const char greetMessage[] PROGMEM = {"Something"};
i
PROGMEMについては、Arduino言語リファレンスも参照してください。

ARMベースのArduino®ボードで同様のことを行うには、変数に対してstatic constを適用する必要があります。

1
static const int Variable = Data;

以下にまとめたように、異なるレベルで使い方が異なります。

  • 名前空間レベル
    • 名前空間レベルでは、変数を示しています。static宣言されているかどうかで異なります。宣言されているときは変数は明示的に静的です。そうでなければ、暗黙的に静的です。
  • 関数レベル
    • 関数内でstatic宣言されていれば、監理される全てのデータは、関数呼び出しの間で利用可能です。
  • クラスレベル
    • クラスレベルでは、static宣言は、全ての型の操作されるデータは、インスタンス間で共有されます。

非動的メモリ割り当て

システムのRAM容量が充分大きいときには、動的メモリ割り当ては、通常、適切な方法です。しかし、組込みシステムのようなマイクロコントローラベースのシステムでは、全てのRAM容量をあてにすることは推奨されません。

動的メモリ割り当ては、ヒープの断片化を引き起こします。ヒープの断片化の影響を受けたRAMの多くの場所は再利用できず、他のタスクに活用される、利用できない部分が残ります。そのうえ、動的メモリ割り当てが、空き領域を作るために、割り当てを解除を進めても、必ずしも、ヒープサイズを削減することにはなりません。ヒープやRAMの断片化を可能な限り避けるには、以下のルールに従ってください。

  • ヒープではなくスタックを優先して使う
    • スタックメモリはフラグメントを起こしません。関数がリターンすると完全に開放されます。一方、ヒープは、解放するよう指示されても、領域を解放するとは限りません。ローカル変数の利用はメモリを解放するので、malloccallocreallocといった関数呼び出しによる、動的メモリ割り当てを使わないようにします。
  • グローバル変数やスタティック変数を削減する(可能であれば)
    • コードを実行中、これらのデータによって占有されるメモリ領域は解放されません。定数は貴重な領域を占有しますが、データは変更されません。
  • 短い文字列リテラルを使う
    • 文字列リテラルは可能な限り短くしてください。1文字は1バイトのRAMを占めます。短ければ短いほどメモリを使用しません。ただし、短くしてもコード領域の複数個所で利用できるわけではありません。必要な時に利用し、可能な限り短くし、他のタスク用にRAM空間を節約してください。
    • 配列も最小限のサイズにすることを勧めます。配列の大きさを変更する必要があるときは、コード内で配列の大きさをいつでも再設定することができいます。配列の大きさをハードコードする手法は、面倒で非効率です。しかし、コードが小さい配列を利用し、それが3個以下であれば、要件を知っていれば、手動での再設定は充分です。これを行うための知的な方法に、大きさが制限された再設定可能な変数の利用方法があります。この方法では大きさの制限を超えない配列を使います。このように大規模なコードで有用です。しかし、配列の大きさの制限を分析し、可能な限り小さくすることが必要です。

reserve関数

コード内のタスクで、reserve()を使えば、操作の結果により文字列の大きさを変更することができます。この関数は、文字列変数用のバッファ空間を予約し、事前に確保します。また、サイズも変更できメモリの断片化も防ぎます。例えば、大きさが変更される文字列変数は、文字列として利用されるよう、int型の変数がラップされます。

以下のコードは、reserve()関数の使い方を示します。

1
2
3
// String_Variable is an String type variable
// Alloc_Size is the memory to be pre-allocated in number of Bytes with unsigned int type
String_Variable.reserve(Alloc_Size);
i
reserve()関数の詳細は、Arduino言語リファレンスを参照してください。

バッファサイズの制御

バックエンドプロセスにも処理を行うために、メモリプールが必要です。定義されたメモリプールの容量によって動作するものです。このバッファはユーザが定義でき、メモリに割り当てる容量を小さくするために、小さくすることもできます。配列変数の大きさを定義するときには、大容量を割り当てて、その三分の一しか使うようなことがないようにすることが重要です。

Arduinoでのシリアル通信を例に、考えていきます。シリアル通信は、Arduinoベースのシステムで、よく使われるサービスです。Arduinoでのシリアル通信は、事前にインストールされたSerialライブラリを使って動作します(ソフトウェアを使ってシリアル通信をエミュレートする外部ライブラリも使えます)。シリアル通信では、バックエンドサービス間で利用するのに必要なメモリプールを、事前定義された大きさのバッファとして定義します。高速なシリアル通信が要件でなければ、メモリ使用量を削減するために、シリアル通信用のバッファの大きさを、再定義することもできます。これは、Arduino IDEのインストールフォルダにある、HardwareSerial.h内の以下のコードを修正することで実現できます。

1
2
3
#define SERIAL_TX_BUFFER_SIZE 64

#define SERIAL_RX_BUFFER_SIZE 64
i
ライブラリの特定のタスクを実行するのに必要なバッファの大きさを最適化するために、外部ライブラリはよく修正されます。

データ型の使用方法の是正

適切なデータ型を実装することはコード全体のアーキテクチャをよくします。開発者がコード内でデータを扱うのに、もっとも簡単なデータ型や、もっともアクセスしやすいデータ型を利用することが望ましいです。しかし、ある型を使うときに利用されるメモリの総量を考慮することも重要です。

データストリームの形式を簡単にし、不正アクセスしないように扱うために、データ型は存在します。データ型での不正アクセスは、コード内でデータが互換性のない形式で扱われることを意味します。データ型を間違えて使わず、全てのデータビットに対し適切な方を利用することがいい方法です。要件に従って注意深く設計しメモリを割り当てることは、設計されたタスクがさらにメモリを必要とするとき、いくらかのメモリ空間を確保するのに役立ちます。

以下の表は、Arduinoでの基本的な型の大きさを示します。

大きさ 値の範囲
boolean 1 true/false
char 1 -128 to 127
unsigned char 1 0 to 255
byte 1 0 to 255
int 2 -32,768 to 32,767
unsigned int 2 0 to 65,535
word 2 0 to 65,535
long 4 -2,147,483,648 to 2,147,483,647
unsigned long 4 0 to 4,294,967,295
float 4 -3.4028235E+38 to 3.4028235E+38
double 4 -3.4028235E+38 to 3.4028235E+38

EEPROMメモリの最適化

EEPROMメモリの最適化は通常必要ではありません。EEPROM領域に配置されるデータは保存領域としてフラッシュメモリを利用しません。さらに、SRAMデータをEEPROMに移すこともよい方法ではありません。SRAMデータは揮発性であることに注意してください。非揮発性のEEPROMにデータを移すことは、EEPROM空間に永続化されてしまいます。

EEPROMに関しては、write操作は制限されていることを知ることが重要です。EEPROMでは、read操作は制限されていません。しかし、write操作の実行は有限回で、通常100,000回が上限です。このように、種に変更されないデータでセンサやモジュールの動作に必要不可欠なパラメータだけを保存することが重要です。さらに、定常的なwrite操作を避けるために、write操作はループの中に置かないようにしてください。これらの操作は、システムが動作中には最小限にとどめる必要があります。

フラッシュメモリでのEEPROMエミュレーション

EEPROMには書き込み操作サイクルに制限がありますが、フラッシュメモリも同様です。双方とも、製造者が規定したライフサイクルの後には、データの保持ができなくなります。EEPROMはNOR型のメモリに基づいていて、フラッシュメモリはNAND型に基づいています。このためEEPROMのほうがフラッシュメモリよりも高価です。EEPROMはバイト単位でデータにアクセスしますが、フラッシュメモリはブロック単位でアクセスします。

ときどき、タスク実行のために開発者はEEPROMを代替ストレージとして使う必要があります。しかし、サイズと動作の特性によりこのことが実用的でないことを学びました。これを解決するために、フラッシュメモリをEEPROMのように使うことができます。Chrisitan Maglieによって作成されたFlashStorageライブラリのおかげで、フラッシュメモリをEEPROMのように使うことができます。

i
FlashStorageライブラリは、フラッシュメモリをEEPROMのように使うのに役立ちますが、このライブラリを使うときは、もちろん、EEPROMの特性を思い出すことが重要です。EEPROMと同様、フラッシュメモリもwriteサイクルに制限があります。ライブラリで実装されている2つの新しい関数のうち、EEPROM.commit()は、ループの中で呼んではいけません。そうした場合、フラッシュメモリのwrite操作サイクルを使い切り、データ保持能力を失ってしまいます。

その他の資料

マイクロコントローラベースのシステムでのメモリアーキテクチャは、幅広いトピックです。このトピックについてさらに学習したい場合は、以下のリンクを参照してください。

  • Microchip® Developer ヘルプサイトの8-bit AVR® Coreの文書。ここでは、8-bit AVR® Central Processing Unit (CPU)の詳細な情報があります。
  • ARMアーキテクチャドキュメントサイト。ここには、異なるARMプロセッサの詳細情報があります。Coretex-M0+とCoretex-M4の技術リファレンスマニュアルを見てください。

参考文献

[1] S. F. Barrett and D. J. Pack, Microchip AVR® Microcontroller Primer: Programming and Interfacing, Third Edition (Synthesis Lectures on Digital Circuits and Systems), Morgan & Claypool, 2019.

[2] J. Y. Yiu, The Definitive Guide to ARM Cortex -M0 and Cortex-M0+ Processors, Second ed., Newnes, 2015.

[3] J. Yiu, The Definitive Guide to ARM® Cortex®-M3 and Cortex®-M4 Processors, Third ed., Newnes, 2014.

オリジナルのページ

https://docs.arduino.cc/learn/programming/memory-guide

最終更新日

March 2, 2023

inserted by FC2 system