この記事では、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ベースの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ベースのマイクロコントローラのメモリは、前述したアドレス形式内で、以下のセクションから構成されます。
- 仮想アドレス
- カーネルコードとデータ
- アプリケーションコードとデータ
- 物理アドレス
- 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 | - |
Arduino®ボードのメモリ使用量を測定する
メモリ使用量統計は、設計したコード構造によって影響のあるリソース管理に対する見通しを理解するのに役立ちます。メモリの負荷要件は、コードがどの程度効率的に設計されたかを見通すためのひとつの統計情報です。マイクロコントローラベースのシステムの資源は有限なので、重要な開発時考慮する要素の一つになります。ソフトウェアは問題や課題を避けるため、常に最大負荷容量に達することなく実行される必要があります。メモリ負荷は、特定のタスクが自由に利用できるRAMやフラッシュストレージに要求される空き容量として観測されます。
Arduino®ボードでのメモリ使用量管理方法を説明していきます。
フラッシュメモリの測定
Arduino®ボードのフラッシュメモリは、Arduino IDEを使って測定できます。前述したように、フラッシュメモリはアプリケーションのコードが格納される場所です。Arduino IDEは、コンパイラの出力コンソールを通じて、フラッシュメモリ資源の資料量を、開発者に知らせます。
例えば、AVRベースのArduino®ボード(Nano)の出力コンソールを以下に示します。
ARMベースのArduino®ボード(MKR WAN 1310)の出力コンソールを以下に示します。
別のARMベースのArduino®ボード(Portenta H7)の出力コンソールを以下に示します。
ボードがAVRベースなのか、ARMベースなのかによってコンパイラの出力が変わることに注意してください。
SRAMのメモリ測定
IDEにより、コードのコンパイルとアップロードに成功しても、突然止まってしまうことがあります。これらの問題は、メモリ資源の占有や割り当てるメモリの不足により発生する問題であることが多いです。これを解決するには、どのコード部分のメモリ要求が利用可能な資源量を超えているのかを理解する必要があります。以下のコード例はAVRベースのArduino®ボードでのSRAM使用量の測定に利用できます。
|
|
ヒープセクションは、実行時に生成される変数が格納される場所だということを思い出してください。コード中の、__heap_start
と__brkval
は以下の通りです。
__heap_start
: ヒープセクションの開始地点__brkval
: ヒープによって使われる最後のメモリアドレスへのポインタ
以下のコード例はARMベースのArduino®ボードでのSRAM使用量の測定に利用できます。
|
|
上記のコードは、Michael P. FlagaによるArduino-MemoryFreeライブリのものです。
EEPROMのメモリ測定
EEPROMのメモリ管理はArduino IDEにより既にインストールされている固有のライブラリを使うことで容易に行うことができます。EEPROM
ライブラリは、EEPROMメモリの読み込み、書き込み、消去に利用できます。以下のコードは、write
関数とread
関数によって1バイトの情報がどのようにEEPROMに格納されるのかと、読み取られるのかを示します。
|
|
以下のコードで示すように、全てのEEPROMメモリを0に設定することで、メモリをクリアすることもできます。
|
|
Arduinoベースシステムのメモリ利用を最適化する
システムがどのようにメモリ資源を利用するのかを知ることは、開発プロセスで最初に推奨される事項ですが、メモリ利用の最適化は全く異なります。開発という用語が示すように、コンポーネントが利用できないことなどによりデバイスの容量が削減されたりするなどの外部要因に依存し、要件が変更、調整されることがあります。このように、限られた少ないメモリ資源で実行できるように、コードアーキテクチャの最適化が必要になることがあります。
メモリ利用の最適化処理は、同じタスクの実行に要するメモリ資源量を削減するとともに、計算の複雑さの削減、タスクの処理に要する余分な時間の削減も意味します。メモリ利用の最適化処理は、より高度なアルゴリズムの開発が必要になりメモリ監理がより適切なものになるので、コード全体の最適化処理にもなります。
メモリ利用の最適化方法を説明していきます。
フラッシュメモリの最適化
フラッシュメモリの最適化は、最も直感的な最適化手法です。いくつかの詳細点を考慮することで、フラッシュメモリの容量である、コンパイルされたコードの容量を大幅に削減できます。
未使用のソースを削除する
新しいソースコードを削除することには、未使用のライブラリとコードの残骸を削除することが含まれます。コードの残骸には、メモリの不要な空間を占めている、もう利用していない関数や浮いた変数が含まれます。これはコンパイルされたコードの大きさを大幅に改善し、コンパイル処理をより明確にします。
処理のモジュール化
処理のモジュール化とは、異なるパラメータを受け取ることで繰り返し、もしくは、継続的に利用されるコードを包む関数を意味します。実装する追加タスクに要求されるメモリ量を削減しつつ、明確なコード構成と性能を維持できる素晴らしい方法です。
これによりコード構成は小さくなり、デバッグの際の理解がとても容易です。また、このためには、コード構成や特定のアルゴリズムを設計する際に開発者は、計算の複雑さを考慮する必要があります。
SRAMのメモリ最適化
マイクロコントローラベースのシステムで、SRAMメモリは恐らくもっとも重要なメモリユニットです。SRAMの使用量を最適化することは信頼性の高いマイクロコントローラベースのシステムを設計するための本質てきな事項です。SRAM不足は通常最も一般的に見られるメモリの問題です。SRAMの最適化はこの型の問題の削減に役立ちます。
行を表示するコマンドの理想的な使い方は、F()
文字列ラッパを文字列に使うことです。以下の例を見てください。
文字列ラッパ
Serial.print()
命令やSerial.println()
命令は、SRAM空間を利用します。これは簡単ですが望ましくありません。理想的な方法は、Serial.print()
命令やSerial.println()
命令を、文字列を囲むF()
文字列ラッパとともに使うことです。例えば、
|
|
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
の使い方を、以下のコード例で示します。
|
|
ARMベースのArduino®ボードで同様のことを行うには、変数に対してstatic const
を適用する必要があります。
|
|
以下にまとめたように、異なるレベルで使い方が異なります。
- 名前空間レベル
- 名前空間レベルでは、変数を示しています。
static
宣言されているかどうかで異なります。宣言されているときは変数は明示的に静的です。そうでなければ、暗黙的に静的です。
- 名前空間レベルでは、変数を示しています。
- 関数レベル
- 関数内で
static
宣言されていれば、監理される全てのデータは、関数呼び出しの間で利用可能です。
- 関数内で
- クラスレベル
- クラスレベルでは、
static
宣言は、全ての型の操作されるデータは、インスタンス間で共有されます。
- クラスレベルでは、
非動的メモリ割り当て
システムのRAM容量が充分大きいときには、動的メモリ割り当ては、通常、適切な方法です。しかし、組込みシステムのようなマイクロコントローラベースのシステムでは、全てのRAM容量をあてにすることは推奨されません。
動的メモリ割り当ては、ヒープの断片化を引き起こします。ヒープの断片化の影響を受けたRAMの多くの場所は再利用できず、他のタスクに活用される、利用できない部分が残ります。そのうえ、動的メモリ割り当てが、空き領域を作るために、割り当てを解除を進めても、必ずしも、ヒープサイズを削減することにはなりません。ヒープやRAMの断片化を可能な限り避けるには、以下のルールに従ってください。
- ヒープではなくスタックを優先して使う
- スタックメモリはフラグメントを起こしません。関数がリターンすると完全に開放されます。一方、ヒープは、解放するよう指示されても、領域を解放するとは限りません。ローカル変数の利用はメモリを解放するので、
malloc
やcalloc
、realloc
といった関数呼び出しによる、動的メモリ割り当てを使わないようにします。
- スタックメモリはフラグメントを起こしません。関数がリターンすると完全に開放されます。一方、ヒープは、解放するよう指示されても、領域を解放するとは限りません。ローカル変数の利用はメモリを解放するので、
- グローバル変数やスタティック変数を削減する(可能であれば)
- コードを実行中、これらのデータによって占有されるメモリ領域は解放されません。定数は貴重な領域を占有しますが、データは変更されません。
- 短い文字列リテラルを使う
- 文字列リテラルは可能な限り短くしてください。1文字は1バイトのRAMを占めます。短ければ短いほどメモリを使用しません。ただし、短くしてもコード領域の複数個所で利用できるわけではありません。必要な時に利用し、可能な限り短くし、他のタスク用にRAM空間を節約してください。
- 配列も最小限のサイズにすることを勧めます。配列の大きさを変更する必要があるときは、コード内で配列の大きさをいつでも再設定することができいます。配列の大きさをハードコードする手法は、面倒で非効率です。しかし、コードが小さい配列を利用し、それが3個以下であれば、要件を知っていれば、手動での再設定は充分です。これを行うための知的な方法に、大きさが制限された再設定可能な変数の利用方法があります。この方法では大きさの制限を超えない配列を使います。このように大規模なコードで有用です。しかし、配列の大きさの制限を分析し、可能な限り小さくすることが必要です。
reserve関数
コード内のタスクで、reserve()
を使えば、操作の結果により文字列の大きさを変更することができます。この関数は、文字列変数用のバッファ空間を予約し、事前に確保します。また、サイズも変更できメモリの断片化も防ぎます。例えば、大きさが変更される文字列変数は、文字列として利用されるよう、int型の変数がラップされます。
以下のコードは、reserve()
関数の使い方を示します。
|
|
reserve()
関数の詳細は、Arduino言語リファレンスを参照してください。バッファサイズの制御
バックエンドプロセスにも処理を行うために、メモリプールが必要です。定義されたメモリプールの容量によって動作するものです。このバッファはユーザが定義でき、メモリに割り当てる容量を小さくするために、小さくすることもできます。配列変数の大きさを定義するときには、大容量を割り当てて、その三分の一しか使うようなことがないようにすることが重要です。
Arduinoでのシリアル通信を例に、考えていきます。シリアル通信は、Arduinoベースのシステムで、よく使われるサービスです。Arduinoでのシリアル通信は、事前にインストールされたSerialライブラリを使って動作します(ソフトウェアを使ってシリアル通信をエミュレートする外部ライブラリも使えます)。シリアル通信では、バックエンドサービス間で利用するのに必要なメモリプールを、事前定義された大きさのバッファとして定義します。高速なシリアル通信が要件でなければ、メモリ使用量を削減するために、シリアル通信用のバッファの大きさを、再定義することもできます。これは、Arduino IDEのインストールフォルダにある、HardwareSerial.h
内の以下のコードを修正することで実現できます。
|
|
データ型の使用方法の是正
適切なデータ型を実装することはコード全体のアーキテクチャをよくします。開発者がコード内でデータを扱うのに、もっとも簡単なデータ型や、もっともアクセスしやすいデータ型を利用することが望ましいです。しかし、ある型を使うときに利用されるメモリの総量を考慮することも重要です。
データストリームの形式を簡単にし、不正アクセスしないように扱うために、データ型は存在します。データ型での不正アクセスは、コード内でデータが互換性のない形式で扱われることを意味します。データ型を間違えて使わず、全てのデータビットに対し適切な方を利用することがいい方法です。要件に従って注意深く設計しメモリを割り当てることは、設計されたタスクがさらにメモリを必要とするとき、いくらかのメモリ空間を確保するのに役立ちます。
以下の表は、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のように使うことができます。
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