ArduinoのFマクロやPSTRマクロについてのメモ

## 雑まとめ

- `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()マクロの使い方](https://qiita.com/ma2shita/items/89ed5b74698d4922efdd)とか。他には[Guide to PROGMEM on ESP8266 and Arduino IDE](https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html)とか。

公式リファレンス[PROGMEM - Arduino Reference](https://www.arduino.cc/reference/en/language/variables/utilities/progmem/)は説明が薄い…。

## 定義を見に行く

### PROGMEMとPSTRマクロ
この2つはCPUアーキテクチャごとの差異を吸収するレイヤなので一緒に書く。

見るとわかりますが、それぞれ微妙にマクロの定義が違います。

#### ESP8266
[Guide to PROGMEM on ESP8266 and Arduino IDE](https://arduino-esp8266.readthedocs.io/en/latest/PROGMEM.html)を見ると、こんな感じになってるらしい。
```c
 #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

```c
// 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

```c
// 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 *` にキャストしてる。

```c
// 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](https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html#ga73084a8bbde259ffae72980354b3f027)で読んだり、[strcpy_P](https://www.nongnu.org/avr-libc/user-manual/group__avr__pgmspace.html#ga5749897c91c479d02054fc02128de482)でコピったり、[snprintf_P](https://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html#ga53ff61856759709eeceae10aaa10a0a3)で使ったり。

### Fマクロ
文字列として`const __FlashStringHelper *`を受ける関数に渡す。

#### 受け取った関数の中では何をしているか
受け取ってすぐ`PGM_P`へキャストして、`strlen_P`とかFlashROMのデータを処理する関数に渡してる。

```cpp
// 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`をそのまま置くしかない[^f-pstr-err]。
    - ESP8266のArduinoライブラリにはFPSTRマクロがあって、`const __FlashStringHelper *`にキャストしてくれる。

[^f-pstr-err]:「statement-expressions are not allowed outside functions nor in template-argument lists」ってコンパイラに怒られる。 [F() gives compilation errors - Programming Questions - Arduino Forum](https://forum.arduino.cc/t/f-gives-compilation-errors/581251)を参照。