雑まとめ
-
PROGMEM
- 各アーキテクチャがもつ「定数データをFlashROMから直接読み込む機能」をArduinoの世界で共通に使えるよう抽象化したもの
-
PSTR
マクロ-
PROGMEM
を使ってASCIIZ文字列定数を書く記述をArduinoの世界で共通に使えるよう抽象化したもの。定義された値の型はPGM_P
というマクロで定義されている型になるが、AVRとESP8266とかだとconst char *
になる。
-
-
F
マクロ-
PSTR
マクロの吐き出す結果がマクロPGM_P
で定義されている型であることは前述したが、これはコンパイラの知るところではないので、PSTR
マクロの結果をconst __FlashStringHelper *
型として返すことでコンパイラが認識できるようにしたもの。
-
はじめに
Arduinoいじっていると、FマクロとかPSTRマクロがよく出てくるんですよね。Qiitaにも記事があって、ArduinoのSRAMを節約できるF()マクロと **_P 関数 && PSTR()マクロの使い方とか。他にはGuide to PROGMEM on ESP8266 and Arduino IDEとか。
公式リファレンスPROGMEM - Arduino Referenceは説明が薄い…。
定義を見に行く
PROGMEMとPSTRマクロ
この2つはCPUアーキテクチャごとの差異を吸収するレイヤなので一緒に書く。
見るとわかりますが、それぞれ微妙にマクロの定義が違います。
ESP8266
Guide to PROGMEM on ESP8266 and Arduino IDEを見ると、こんな感じになってるらしい。
#define PROGMEM ICACHE_RODATA_ATTR
#define ICACHE_RODATA_ATTR __attribute__((section(".irom.text")))
#define PGM_P const char *
#define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];}))
AVR Atmel's SAM3X
// https://github.com/arduino/ArduinoCore-sam/blob/790ff2c852bf159787a9966bddee4d9f55352d15/cores/arduino/avr/pgmspace.h#L32-L34
#define PROGMEM
#define PGM_P const char *
#define PSTR(str) (str)
AVR libc
// excerpt from http://svn.savannah.gnu.org/viewvc/avr-libc/trunk/avr-libc/include/avr/pgmspace.h?view=annotate
#define __ATTR_PROGMEM__ __attribute__((__progmem__))
#define PROGMEM __ATTR_PROGMEM__
#define PGM_P const char *
# define PSTR(s) (__extension__({static const char __c[] PROGMEM = (s); &__c[0];}))
Fマクロ
PSTRの結果をconst __FlashStringHelper *
にキャストしてる。
// https://github.com/arduino/ArduinoCore-API/blob/e03b65374c614130aa1b11597e07b3b5089a726d/api/String.h#L45
#define F(string_literal) (reinterpret_cast<const __FlashStringHelper *>(PSTR(string_literal)))
定義された値の使い方
PROGMEM / PSTRマクロ
アクセス方法がCPUアーキテクチャ依存なので、それをwrapしたライブラリ経由で使います。具体的にはavr/pgmspace.h
などで定義されてる関数を使ってFlashROMからメインメモリに引っ張ってくることになる。
たとえばpgm_read_byteで読んだり、strcpy_Pでコピったり、snprintf_Pで使ったり。
Fマクロ
文字列としてconst __FlashStringHelper *
を受ける関数に渡す。
受け取った関数の中では何をしているか
受け取ってすぐPGM_P
へキャストして、strlen_P
とかFlashROMのデータを処理する関数に渡してる。
// https://github.com/arduino/ArduinoCore-API/blob/e03b65374c614130aa1b11597e07b3b5089a726d/api/String.cpp#L260
String & String::operator = (const __FlashStringHelper *pstr)
{
if (pstr) copy(pstr, strlen_P((PGM_P)pstr));
else invalidate();
return *this;
}
どう使っていくか
- 関数に定数文字列を渡すとき
const __FlashStringHelper *
を受け取る引数があるならFマクロで包んで渡せば良さそう。 - 自作関数内で完結するときはPSTRマクロで包んで
*_P
関数に渡せば良さそう。 - ライブラリとかで外から受け取るときは
const __FlashStringHelper *
で受け取る関数をつくって、PSTR
はライブラリに閉じ込めると良さそう。- 定数として外に置くときFマクロやPSTRマクロは使えず
PROGMEM
をそのまま置くしかない1。 - ESP8266のArduinoライブラリにはFPSTRマクロがあって、
const __FlashStringHelper *
にキャストしてくれる。
- 定数として外に置くときFマクロやPSTRマクロは使えず
-
「statement-expressions are not allowed outside functions nor in template-argument lists」ってコンパイラに怒られる。 F() gives compilation errors - Programming Questions - Arduino Forumを参照。 ↩