読者です 読者をやめる 読者になる 読者になる

Arduinoのメモリ消費を抑える

最近、ATtiny2313を使って、MAX31855Kを利用したK型熱電対のディスプレイを作製しようとしていました。

MAX31855KはAdafruitにArduino用のサンプルライブラリがあり、これを利用しない手はありません。ですので、表示部としてキャラクタ液晶を使うとして、公式のLiquidCrystalライブラリと、Adafruitのライブラリを利用してしまえば簡単にできるでしょう。

と思っていましたが、一筋縄ではありませんでした。tiny2313はメモリがプログラムメモリで2kByte、SRAMが128Byteしかありません。実にArduino Unoの1/16です。この問題についてある程度スケッチを書いてコンパイルしてから気づいたのですが、一度乗りかかった船、何とかならないものかとがんばってみました。

なお、Arduinoでtiny2313を利用するために、kosakalabさんの記事のものを利用しています。Arduino1.6では若干の修正が必要ですが、その件の解説は行いません。

浮動小数点を使わない

これは公式にも書かれている手法ですが、必須です。
Adafruitのライブラリにはdoubleが利用されているため、修正します。
MAX31855Kは符号付き14bitの値を返し、生の値に0.25を掛けたものが実際の値になります。
f:id:rinpoyo:20160607011628p:plain
これを読んだ値がすでに4倍されたものとすることにし、

/*  uint32_t v = spiread32()>>18; // となっているような状態で*/
  if (v & 0x2000) { // 負の値の時
    lcd.write('-');
    temp ^= 0x3fff;  // 補数表現を戻す
    temp += 1;
  }
  lcd.write('0'+((v/4000)%10);    // 1000の位
  lcd.write('0'+((v/400)%10);     // 100の位
  lcd.write('0'+((v/40)%10);      // 10の位
  lcd.write('0'+((v/4)%10);       // 1の位
  lcd.write('.');
  lcd.write('0'+(((v&3)*25)/10);  // 0.1の位
  lcd.write('0'+((v&1)*5);        // 0.01の位

などと書くことができます…

EEPROMを使う

Arduinoではグローバル変数を用いて定数を利用している例がたまにありますが、そういうものはEEPROM上に書き込み、その都度読み出すのが良いです。プログラムメモリの節約と、SRAMの節約が期待できます。

tiny2313では128Byteありますので、うまく使えばかなり余裕ができます。

例として、LiquidCrystalのCustomCharacterサンプルの場合、

// make some custom characters:
byte heart[8] = {
  0b00000,
  0b01010,
  0b11111,
  0b11111,
  0b11111,
  0b01110,
  0b00100,
  0b00000
};

と宣言し、setup関数内で

  // create a new character
  lcd.createChar(0, heart);

と利用していますが、heartはこれ以降利用されません。
このheartのデータをあらかじめEEPROMに書き込んでおき、以下のようにEEPROMを読んで利用してやると良いです。

  byte temp[8];
  for (int i = 0; i < 8; i++) {
    temp[i] = EEPROM.read(i);
  }
  lcd.createChar(0, temp);

Printを利用しない

LiquidCrystalライブラリはPrintクラスを継承していますが、少しサイズが大きめです。
下記のようにコメントアウトします。

/* クラス定義 */
class LiquidCrystal{// : public Print {
/* using部分 */
//  using Print::write;

この変更によって、Printが利用できなくなりますので、

void printStr(uint8_t* str){
  for(;(*str)!='\0';str++)
    lcd.write(*str);
}
printStr("helloworld.");

などと書く必要があります。

ポートを直接制御する

I/OにはpinMode,digitalWrite,digitalRead等を利用しますが、これらの関数はそれぞれ100Byteほどプログラムメモリを消費しています。ライブラリ内を含むすべての呼び出し部分を編集する必要があり、ハードウェアへの依存が大きくなってしまいますが、仕方ありません。

むすび

このあたりの対策をしたところで、ようやく収まりました。
対策を進めるに従ってArduinoに依存しないコードを書くことになってしまい、結果としてAtmelStudioで開発した方が良いのでは、という気もしてきます。
しかし、Arduinoのライブラリを利用することによって、既存の資産が利用できることは大きな利点です。これらの資産を利用し、高速なプロトタイピングに役立てたいところです。