ATtiny85へのコード最適化
前回はArduinoでの動作確認が終了いたしました。
今回はATtiny85に実装するためにArduinoで使用していたコードの最適化を行いたいと思います。内容的には以下の通りです、このリストを基に一つづつ潰していきます。
最適化ポイント
- WireライブラリをTinyWireライブラリに修正
- タイマ割り込み(Timer2)を8bitタイマー(Timer0)使用に修正
- Arduino関数からレジスタ操作に修正
- 外部割込み見直し(ピンがSCKと競合している為)
- チャタリング防止機能見直し
- スリープ復帰方法見直し
最適化
WireライブラリをTinyWireライブラリに修正
まずはこれを行わないと始まりません。Wire.h→TinyWireM.hに修正しましょう。手順は以前の記事にまとめてあります。
タイマ割り込み(Timer2)を8bitタイマー(Timer0)使用に修正
ATtiny85ではMsTimer2.hは使用できません。なぜかというとMsTimer2.hはTimer2(非同期付き8bitタイマ/カウンタ)を使用しており、残念ながらATtiny85にはTimer0(8bitタイマ/カウンタ)しか実装されていません。。。
せっかくなのでTimer2を少し説明します。分解能的にはTimer0と同じ8bitタイマ/カウンタですがASSRという非同期状態レジスタを有しており、システムクロックとは非同期で使用することが出来ます。なのでスリープモードの時にも割り込み動作などのタイマ/カウンタ機能を使用することが出来るんですね。
話を戻すとATtiny85に最適化するためにTimer0を使用する必要があるという事です。今回は比較一致動作(CTC)を使用して50ms毎のタイマ割り込みハンドラーを使用して各カウンタをねじ込んでいこうと思います。Timer0の部分を抜き取ったコードを下に載せました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
volatile bool k;//BME280読み取りフラグ volatile uint8_t tcnt;//タイマ割り込みカウンタ volatile uint8_t scnt;//スリープ待機カウンタ //Timer0割り込みハンドラー ISR(TIM0_COMPA_vect){ if(tcnt>=20){//約50ms×tcnt k = !k; tcnt=0; scnt++; } tcnt++; } void setup() { //Timer0設定@内部クロック4MHz TCCR0A = 0b00000010;//CTC,比較出力切断 TCCR0B = 0b00000101;//1024分周 OCR0A = 195;//49.92ms //比較A TIMSK |= 0b00010000;//比較A割り込み許可 } |
Timer割り込み内フラグ/カウンタ
- k : BME280の読み取りフラグ
- tcnt : タイマ割り込みをループカウントして所定の時間を作っています。
- scnt : スリープまでの待機時間をカウントしています。
Arduino関数からレジスタ操作に修正
表題の通りArduino関数を廃止してレジスタ操作に切り替えます。意図は容量削減のため。
とは言いつつもほとんどArduino関数を使用していませんでした。唯一使用していたのはI2Sのデータ送信間隔の調整に使用していたdelay関数のみ。
また、いけないかもですがdelay関数をすべて取り除きました(普通に動いたので取り合えずこのまま進めます)。delayって基本的に使いたくないし、、、
外部割込み見直し(ピンがSCKと競合している為)
一番予想外だったのがこれ!外部割込みINT0がI2Cのクロック(SCL)とピン配が競合しているんですね、、、
クロックでHigh-Lowしてたら勝手に割り込みは行っちゃうんじゃないの?と思い外部割込みを使用するのは断念。スリープの解除はリセットピンを使用します。しぶしぶタクトスイッチを2個の構想に修正です。
チャタリング防止機能見直し
外部割込みが使えなくなったという事でチャタリング防止コードも変更です。本来であれば以下のようなフローでチャタリング防止を行う予定でした。
- 外部割込み
- 一定時間カウント(カウント中割り込みが来たらカウントリセット)
- ボタン押下フラグ更新
外部割込みじゃなくてピン変化割り込み(PCINT)を使用しようと思いましたが使い勝手が悪く断念。
ポーリング内でボタン押下検知後に適当なカウンタを持たせてチャタリング防止をする仕様にしました。
1 2 3 4 5 6 7 8 9 10 11 |
x=~(PINB>>3)&1;//PB3状態抽出 while(x){ static uint32_t cnt;//チャタリング防止カウンタ cnt++; if(cnt>=100000){ y=1; x=0; cnt=0; scnt=0; } } |
スリープ復帰方法見直し
先ほど外部割込みの時にも触れましたが、単純にプルアップしたタクトスイッチをリセットピンに付けてスイッチ押下によりスリープ状態を解除するようにしました。
コード容量
さあ、上記の最適化を行った結果どれくらいコード容量に変化があったかというと、、、
コード容量比較
- Arduinoコード:7474byte
- ATtiny85 :3818byte
だいぶ容量削減が出来ました当初の1/2くらいです。さあ、ピンも2ピン余ってるし何か機能を追加したい欲に駆られますが考えます。
腑に落ちないところ
BME280の値の分解方法
何かというと、BME280から得た値を1桁づつ分解してLCDに表示させているのですが、その分解方法がArduinoからATtiny85に移行させたらうまく演算できなくなってしまいました。半日くらい悩んでも解決しなかったので、半ば強引に数字を分解しました。
■ATtiny85移行後うまくいかなかった演算式
1 2 3 4 5 6 7 |
m=100000; n=10000; for(int i=0;i<2;i++){//整数 dataWrite(0b00110000|(lhum%m/n)); m=m/10; n=n/10; } |
lhumにはBME280から得た湿度値(例えば10000(実際は10.00%))が入ります。整数の部分を分解するために2回forを回して剰余と除算を駆使して分解しました。でもなぜかできない!しかもできないのは湿度の整数部分だけ。他の温度とか気圧も同様な演算式を使っているのにそっちは普通に出来ているんですね、、、、
■強引に分離式
1 2 |
dataWrite(0b00110000|hum/10000); dataWrite(0b00110000|hum%10000/1000); |
あれ、こっちの方がすっきりしてない?という突込みは無しで、、、ループを使用してスマートに演算したかったんです。これはすんなりOKなのでこれで進めます。
“sleep”のLCD表示位置
LCDの表示がおかしいんですね。ある一部の時のみ。
スリープに入る前に”sleep”とLCDの2行目に表示するようにしていたのですがATtiny85に移行後1行目に表示されるようになってしまったんですね。
■スリープフロー
- タイマ割り込みごとにscntインクリメント
- scntが10(10秒)になったらwhileループに入る
- LCDリセット
- 2行目に”sleep”表示
- scntが12になったらスリープ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
//スリープモード条件 uint8_t r=0; while(scnt>=10){ switch (r){ case 0: commWrite(0x01); r=1; break; case 1: commWrite(0b11000000); dataWrite(0b01110000|0b0011);//s dataWrite(0b01100000|0b1100);//l dataWrite(0b01100000|0b0101);//e dataWrite(0b01100000|0b0101);//e dataWrite(0b01110000|0b0000);//p dataWrite(0b00100000|0b1110);//. dataWrite(0b00100000|0b1110);//. dataWrite(0b00100000|0b1110);//. r=2; break; } if(scnt>=12){ commWrite(0x08);//display-off/cursor-off/画面反転-off commWrite(0x01); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_mode();//スリープ実行(復帰はリセット) } } |
なんでか1行目にsleep表示が出てしまう。
うーん、わからない。もうちょっと探ってみます。
おわりに
今回はArduinoで書いたコードをATtiny85用に最適化しました。
一部腑に落ちないところがありますが、基本的な機能は問題なく動いています。あとはハード部分の筐体を加工して実装していきます。
当初より記事数がオーバーしそうです。すいませんがあと2記事くらいお付き合いいただければと思います。
ではでは~
コメント