Arduinoで、小っちゃいIC(ATTiny)を使う方法
ちっちゃいICを使いたい!
Arduino、始めたばかりですが超楽しいです。(いや始めたばかりだから楽しいのかw)
お金が無いせいで、シールドとかにはまだ手を出してなくて、Arduino UNOとブレッドボード、それと安く手に入る部品だけを使って遊んでいます。
ブレッドボードで動作確認したら、ICをUNOから引き抜いて、ユニバーサル基盤に作った回路に組み込んでみたり。こうした使い方をしていることもあり、ATmega328Pを追加で買ったりしてるのですが、ATmega328Pは1コ250円もして、少々お高いのが気になってました。
それに28PIN-DIPって簡単な用途には大きくて配線もたいへん。
将来量産(なにを?!いや何か絶対!w)を見据え、もっと物理的にちっちゃいICを試してみたい。でも表面実装(SMD)版とかハンダ付け超ムリ!
ATTinyはDIPでもちっちゃい!
というわけで目をつけたのがATTinyシリーズ。DIP版でも足の数が少ないので、ちっちゃいです。特に13A!
上からATTiny13A、ATTiny2313、一番下がATmega328P
試しに20本足のATTiny2313-20PUと、8本足のATTiny13Aを購入してみました。
ざっくりSPEC表
SPEC | ATmega328P | ATTiny2313 | ATTiny13A |
---|---|---|---|
価格 (2014.1.26現在) |
250円 | 150円 | 50円 |
DIP PIN数 | 28 | 20 | 8 |
ROM | 32Kbyte | 2Kbyte | 1Kbyte |
RAM | 2Kbyte | 128byte | 64byte |
ATTinyは大きさだけでなく、性能も、ちっちゃい!w
どこまで使えるかわかりませんが、ちょっと触ってみようと思います。
1. Arduino UNOで、ちっちゃいIC(ATTiny)を開発する
Arduino UNOがあれば(それが互換機でも)ATTinyの開発ができます。UNO以外に必要なのは、ブレッドボードと抵抗、コンデンサ、ジャンパーピン、LEDなど。Arduino入門編をやってる方は持っているものばかりだと思います。
Arduino UNOでATTinyなどを開発するやり方は、kosakalabさんの、下記記事で超詳しく紹介されています。
Arduino IDEでATtiny他の開発(Arduino-ISP編)
私も記事の通りに進めることでATTiny13AとATTiny2313でのLチカに成功しました。
素晴らしい記事を書いてくれたkosakalabさんには、本当に感謝です!
2. ROM/RAM使用量を確認できるようにする
ROM使用量はIDEでビルドするだけで表示される
ATTinyはROM(正確にはFLASHROM)やRAM(正確にはSRAM)が小さいので、プログラムも小さくしなければ入らなくなります。
Arduinoのプログラムが、どのくらいROMやRAMを消費するか、Arduinoのいわゆる「Lチカ(blink)」を使って確かめてみましょう。
** Arduino IDEに付属のBLINKのコード (コメントは外してあります) **
(ATTiny対応にPIN番号だけ変更してあります。)
int led = 0; // ATTiny2313用に変更
void setup() {
pinMode(led, OUTPUT);
}
void loop() {
digitalWrite(led, HIGH);
delay(1000);
digitalWrite(led, LOW);
delay(1000);
}
910byte。え!Lチカだけで?こんなに大きいんだ。。。13A、Lチカ以外できないんじゃ?
と、一抹の不安を感じつつ。。
910byteというのはFLASH ROMの使用量です。まだRAMはどのくらい使ってるのかは解りません。
RAM使用量の調べ方
というわけで、RAM使用量も確認してみます。
ArduinoのRAM使用量の調べかたは、下記で紹介されています。
Arduinoでのメモリ使用率の調べ方
なんと、elfファイルからdumpしろ。ということらしいですね!
いきなり組み込み系な感じになって来ました。なんかArduinoの可愛さが薄れていくような。。。w
いえいえ、ちっちゃいATTinyのためです。やってみましょう。
> avr-objdump -h プロジェクトファイル名.elf
と実行してみます。
Lチカのプログラムだけですと、.textと.bssしか出てきませんでした。よく見るとconstデータがありませんね。
これだとちょっと説明にならないので、少しプログラムを改造して、もう1度やってみます。
(Lチカのタイミングを少しおかしくしただけのものです)
int cnt;
const int led = 0;
const int a[] = {200,400,800,1000}; // Lチカのタイミング
void setup() {
pinMode(led, OUTPUT);
}
void loop() {
int v;
v = a[cnt];
cnt++;
if(cnt == 4){
cnt = 0;
}
digitalWrite(led, HIGH);
delay(v);
digitalWrite(led, LOW);
delay(v);
}
今回は、LEDのピン番号の変数と
このスケッチをビルドすると、Lチカタイミング用のテーブルをconstに追加してみました。
これをビルドしてみますと、
となりました。const変数を追加したので、今度は.dataというものが出てきました。
長くなりましたが、ROMとRAMの容量をまとめると、
ROM使用量 = .textと.dataの合計 (今回の例では 982byte)
RAM使用量 = .bssと.dataの合計 (今回の例では 19byte)
となります。
(RAMはもう少し複雑で、ヒープやスタックと共有で利用していますので、正確に言うと上記以外にも使われています)
RAMの使われ方については、下記サイトの説明がとても参考になります!
Arduino Unoのメモリ
3. プログラムをちっちゃくする!
さて、いよいよプログラムをちっちゃくするのに挑戦します!
そんなんかんたんにできるんか〜?と気になるところですが、さすがはArduino、さすがはインターネット!
いろいろな手法があるようです。
ROMを減らす方法。単純に処理する関数をつくろう。
簡単にやれることから
Arduinoでは、いろいろな関数やライブラリが利用できます。
これらは、とても「書きやすく、とっつきやすい」ものですが、中を覗いてみると、様々なケースに対応できるようにするためか、結構なコード量だったりします。
先ほどのLチカにも出て来たpinMode()
やdigitalWrite()
を置き換えるだけでも結構削減できるので、やってみたいと思います。
※pinMode()
やdigitalWrite()
がどのような処理を行っているかについては、下記サイトで詳細に解説されています。
Arduionoソフトウェアの内部構造: pinMode()
Arduionoソフトウェアの内部構造: digitalWrite()
上記サイトの情報をもとに、pinMode()とdigitalWrite()を書き換えてみます。
実はポート叩いてるだけなんで、マクロを作ります。
// ATTiny2313用。D0ピンIOマクロ
#define PMD0_OUTPUT() (DDRD |= _BV(0)) // pinMode(0,OUTPUT)
#define DWD0_HIGH() (PORTD |= _BV(0)) // digitalWrite(0,HIGH)
#define DWD0_LOW() (PORTD &= ~_BV(0)) // digitalWrite(0,LOW)
あとは、pinMode()やdigitalWrite()と置き換えるだけ。
int cnt;
// const int led = 0; // ←マクロにしたので要らなくなりました。
const int a[] = {200,400,800,1000}; // Lチカのタイミング
void setup() {
PMD0_OUTPUT(); // pinMode(0,OUTPUT)
}
void loop() {
int v;
v = a[cnt];
cnt++;
if(cnt == 4){
cnt = 0;
}
DWD0_HIGH(); // digitalWrite(0,HIGH)
delay(v);
DWD0_LOW(); // digitalWrite(0,LOW)
delay(v);
}
でビルドしてみると。
982→624byte!300byte以上も削減できました。
トレードオフできることはトレードオフしてみる。
さきほどのpinMode()とdigitalWrite()の例は、もとある機能を変えずにコード圧縮を実現していました。
しかし、全ての機能を正確に実現することは、そんなに大事でないかもしれません。
たとえば、さきほどのLチカ。
Lチカであれば、LEDの間隔が多少ずれても、そんなにたいしたことではないはずです。
精密なタイマーの場合は大問題ですが、そもそも精密なタイマーにdelay()は使わないでしょうw
というわけで、delay()
にもメスを入れてみます。かなり大胆にw
extern volatile unsigned long timer0_millis;
void deeeelay(unsigned long ms) {
unsigned long exptim = timer0_millis+ms;
while (timer0_millis < exptim){}
}
もう、あまり人におススメできる内容から脱線している気がしますw
冒頭で外部参照しているtimer0_millis
は、hardware/arduino/cores/arduino/wiring.c
などで宣言されているグローバル変数で、起動時からの経過時間(但し限界まで行くと0に戻りまたカウント)をmsで記録しています。millis()
を使っても良いのですが、関数呼ぶ分オーバーヘッドがあるので、直接参照しています。
で、中は見ての通りで、指定された時間が来るまで永久ループしてるだけです。
で、コレの何が「トレードオフ」なのかと言うと、delay()の1/1000の精度しかありません。気になる方はdelay()側のコードを見てください。
上記を使うコード部は、delay(v)
をdeeeelay(v)
にするだけなので、割愛します。
ビルドしてみると、
530byte!
さらに100byte近く削減できました!
これ以上は、違うところ(本来触ってはいけないところ)を触らなければいけなさそうな気がします。。ので、そろそろ止めときます。(ここに書くのではなく、自分で勝手にコッソリ進めますw)
※実は今、ATTiny13AでTone()ぽいものを無理矢理動かしたくて、wiring.cを改造しまくってたり。。。
RAMを減らす方法。.dataを減らしてROMへ。PROGMEMを使う。
さて、最後に.dataが使用するRAM容量を減らす方法を紹介して、この記事をしめたいと思います。
.dataはROMにも入ってるくせにRAMにも入っているという邪魔者です。
「データは入れときたいけど、使うときすぐに使いたいから起動するときROMからRAMに読み込んどく」ということをやってるかららしいです。
でも、大きなデータって主に配列だと思うのですが、これ、全部の要素を頻繁に上から下まで順番に読み込む、なんて使い方するんでしょうか。普通はあまりしないと思います。
通常は、何番目のデータを読むか?という指定をして、ダイレクトに使う部分のデータだけを読み込むことの方が多いと思います。こうした使い方だと、PROGMEMという方法が使えます。
使い方は、下記ページでとても詳しく教えてくれます。教わりました。
Arduino Unoのメモリ
※上記記事はArduino UNOを例に記載されていますので、ATTinyだとメモリ量やアドレスなどが異なります。
まとめ
ATTinyに入れられるのは、ちっちゃいプログラムだけ!ちっちゃいは正義!ということでした。
最後まで読んでいただき、ありがとうございました!
間違いなど、ありましたら教えて頂けたらうれしいです。どうかよろしくお願いします〜。
オマケ
Arduinoで作ったBeep音楽器です。(これはATTinyではなくATmega168P使いました)
[InstagramにショートMovie Upしてます!]http://instagram.com/p/inYeIpnCkl/
つづき
2014.12.17更新
つづきを書きました!いろいろあるATTinyを何点かピックアップして紹介してみました〜!