マイクロコントローラの場合、LEDを点滅させるプログラムは以前から同様の例です。 この記事では、例としてAtmel ATTiny15コントローラーを使用して、このようなプログラムを最小化する実験の結果を示します。
![画像](http://habrastorage.org/files/699/e19/f3f/699e19f3fdef4fdbbaafd71d7a695a1a.png)
UPD:コメントには、 12バイトのレコードソリューションへのリンクがありました。 ブラボー!
UPD2:コントローラーに対する暴力を通して、私はもう2バイト勝ちました。
UPD3:そして、コントローラーに対する暴力をさらに増やした別のソリューション 。
UPD4: 別のオプション -オプション2および3のように、1つの命令で(ただし、すべてのプログラムメモリが同時に実行されます)。
UPD5: FUSEビットを使用してコントローラーピンの1つにクロックを発行する機能を使用するバリアント
コントローラーについて
コントローラーの機能はわずかです。 クロック周波数は、内部ジェネレーターから1.6 MHzです。 ハードウェアリセットとSPIを介したフラッシュの可能性をgivingめずに使用できる空きピンは5つのみです。 2つのタイマーとADCがあります。 メモリ-64バイトのEEPROM。 RAMはなく、32個の汎用レジスタと、深さが3を超えることのできないスタックがあります。 AVR-GCCはそのようなコントローラーの使用を拒否します-アセンブラーの使用を提案します。
![画像](https://habrastorage.org/files/e6f/b47/070/e6fb47070cfb44c48bbd8d0a7e20526f.png)
ツールキット
オペレーティングシステム-Open Suse Linux 13.1 開発環境はWineの下で実行される AVR Studio 4.12 です。 プログラマー-AVRDUDEを実行するUSBASP。 プログラマーはコントローラーに直接接続されており、ファームウェア中および実験中の両方で電力を供給します。
問題-プログラマーがリセットを保持
コントローラのリセット信号は反転します-高レベルは通常動作を意味し、低レベルはリセットを意味します。 ほとんどすべてのAVRのRESET信号は、最初のコントローラーレッグを介して接続されます。 コントローラをリセットする機能に加えて、この信号はインサーキットプログラミングのプロセスで決定的な役割を果たしているため、プログラマに巻き込まれます。 FUSEビットを再構成することにより、このコントローラー出力を汎用出力に変えることができます-コントローラーはコントローラーからの信号によってリセットされなくなりますが、いわゆる高電圧プログラマーなしではプログラミングされません。
USBASPはこのピンを常にLowに保ち、プログラマーが接続されている間はコントローラーが動作しないようにします。 デバッグの利便性のために、プログラムで(USBASP APIを見つけて)、またはハードウェアでRESETコントローラーを上げることができる必要があります。 最も簡単に実現できるものとして、スイッチの形のハードウェアオプションを選択しました。
![画像](https://habrastorage.org/files/757/cae/299/757cae2999794dba8ad06c4cb5b091b6.png)
LED点滅
アセンブラで作業するときに特定のコントローラフットに矩形パルスを発行するような単純なことでも、最適なソリューションを探すための刺激的な検索になります。 間違いなく、プログラマーはオペレーティングシステムとコンパイラの気まぐれに依存しないため、必要に応じて機器を制御できます。
遅延の通常の実装は、空の命令のサイクルを編成することで構成されます。このサイクルでは、コントローラーはピンの切り替え間で回転します。 これは古典的なdelay_msで、arduinoに愛されています。 ここには少なくとも2つのマイナスがあります。短いマイナス-コントローラーをスリープ状態にすることはできません。長い-コントローラーは他のタスクを実行できません。 実際、割り込みがあると、遅延用に慎重に計算されたクロックサイクル数ではそれが提供されなくなります。ハンドラーでコントローラーが費やした時間が遅延時間に加算されます。 もちろん、プロセッサで修正を行い、その後修正修正を行うことができます(プロセッサに異なる長さの分岐がある場合)-結果は非常に複雑なコードであり、正確性を証明することは困難です。
そのため、タイマーに遅延を実装する必要があります。 ATTiny15コントローラーには、このようなタイマーが2つあります。 両方のタイマーは最大255を読み取り、割り込み要求を発行できます。 タイマーのクロックソースは、外部信号または内部クロックの分周器にすることができます。 分周器を使用すると、クロックから分数周波数を受信できます-F / 1、F / 8、F / 64、F / 256、F / 1024。 デフォルトでは、コントローラーは1.6 MHzの周波数で動作します。1024の分周器を使用する場合、タイマー増分の周波数は1562.5 Hzになります。 割り込みは256番目の増分ごとにタイマーによって発行されるため、256でさらに除算され、最終周波数は約6Hzになります。 LEDの点滅にはまったく問題ありません。
ファームウェアの最初のバージョン-良い形のすべての規則によって
ファームウェアの開始時には、すべてのAVRに割り込みハンドラーに切り替えるための指示が含まれている必要があります。 これは、x86の状況と非常に似ていますが、メモリの先頭に命令を保存するのではなく、ハンドラーのアドレスを保存します。
ATTiny15には、このような命令が9つ必要です。 最初のハンドラー、たとえばリセットハンドラーは、実際にはファームウェアへのエントリーポイントです。
rjmp reset reti reti reti reti rjmp timer0 reti reti reti
欠落しているハンドラーは、割り込み戻り命令に置き換えられます。 しかし、それらは決して呼び出されません-対応する割り込みは禁止されています。 つまり、
rjmp reset
後のこれら4つの
reti
rjmp timer0
、
rjmp timer0
その場所にあるためにのみ必要です。
プログラムの仕事は、タイマーの事前選択の設定、
ldi r31,(1<<cs00 ) | (1<<cs02) out tccr0,r31
タイマー割り込みを有効にする、
ldi r31,1<<toie0 out timsk,r31
プロセッサの割り込み処理を有効にします。
sei
コントローラーの出力7をプッシュプルモードに切り替え、
ldi r31,0b100 out ddrb,r31
無限ループでフリーズする
lp: rjmp lp
割り込み処理は、レジスタの反転と、この値をコントローラレッグに出力することで構成されます
timer0: com r31 out portb,r31 reti
アセンブリ後、40バイトのマシンコードが得られました。
![画像](http://habrastorage.org/files/38f/9a1/197/38f9a11970fb4fb98d10a1a96fb1f1e8.png)
プログラムを短くする
アイデアは表面にあります-割り込みとタイマーのみが割り込みから機能するため、コントローラーは割り込みテーブルの他のセルには決してありません-これらのセルをコードで占有します:
.include "tn15def.inc" // 4 ldi r31,(1<<cs00 ) | (1<<cs02) out tccr0,r31 ldi r31,1<<toie0 out timsk,r31 // rjmp reset rjmp timer0 // () reset: // sei // , // B ldi r31,0b100 // B, 2 ( 0) push-pull //, 51 out ddrb,r31 // lp: rjmp lp // 0, 6 timer0: com r31 // r31 out portb,r31 // - , reti //
![画像](http://habrastorage.org/files/c29/f4f/e43/c29f4fe4311540f68c8e638640ccb782.png)
26バイトを受信しましたが、まだできますか?
できる!
タイマーからの割り込みハンドラーをその場所にすぐに配置し、遷移を取り除き、2バイトも保存します。
ldi r31,(1<<cs00 ) | (1<<cs02) out tccr0,r31 ldi r31,1<<toie0 sbr timsk,toie0 // 0 rjmp reset // 0, 6 com r31 // r31 out portb,r31 // - , reti // // () reset: // sei // , // B ldi r31,0b100 // B, 2 ( 0) push-pull //, 51 out ddrb,r31 // lp: rjmp lp
組み立て後、24バイトが判明しました。
![画像](http://habrastorage.org/files/699/e19/f3f/699e19f3fdef4fdbbaafd71d7a695a1a.png)
わずか6バイトのコストで少しエネルギーを節約します
コントローラーは割り込み処理とは何の関係もないため、タイマーを作動させたまま、プロセッサーコアを緩和する価値があります。 これは、MCUCRレジスタフラグSE、SM1、SM0によって制御される
SLEEP
命令によって実行されます。 コントローラーにはいくつかのスリープモードがあり、最も深いパワーダウンから始まり、コントローラーはウォッチドッグタイマーのウェイクアップ、リセットまたは出力状態の変更のみが可能で、カーネルが停止すると待機で終了しますが、タイマー、ADC、およびその他の周辺機器は動作します。 これは私たちに適したモードです。SEフラグのみが設定されている場合に設定されます。
割り込みを起動して処理した後、コントローラーはSLEEPの後に次の命令を実行しようとすることを覚えておくことが重要です。つまり、スリープループをループする必要があります。
.include "tn15def.inc" // 0 - // - 1.6 1024 // 1024 // TCCR0 CS00 CS02 //, 27, 9 ldi r31,(1<<cs00 ) | (1<<cs02) out tccr0,r31 // 0 // TIMSK TOIE0 //, 20 ldi r31,1<<toie0 out timsk,r31 // 0 rjmp reset //////////////////////////////////////////////////////// // 0, 6 com r31 // r31 out portb,r31 // - , reti // //////////////////////////////////////////////////////// // () reset: // MCUCR ldi r31,(1<<SE) out mcucr,r31 // sei // , // B ldi r31,0b100 // B, 2 ( 0) push-pull //, 51 out ddrb,r31 // lp: sleep rjmp lp
![画像](http://habrastorage.org/files/9b5/1c9/0ad/9b51c90ad36244dca1395938d4491855.png)
アセンブラープログラミングは、プログラムのサイズと速度を最適化する最も時間のかかる方法です。 ここで最も興味深いタスクは、最適化されたコードを(何らかの基準で)生成し、このデバイスで最適なコードを取得できないことを証明するようなコンパイラーとJava言語エンジンの開発です。
次の記事では、同じコントローラーに基づくUSART <-> 1wireコンバーター。
読むのが面白い
githubでのプロジェクト ;
エドガー・ダイクストラ。 特集記事
コントローラーへのDatashit ;
みんなのためのエレクトロニクス 。
UPD:コメントには、 12バイトのレコードソリューションへのリンクがありました。
つまり、ウォッチドッグタイマーと、リセット時にRONがリセットされないという事実を使用するという考え方です。
LDI R16,(1<<WDE) | (1<<WDP2) | (1<<WDP1) OUT WDTCR,R16 OUT DDRB,R16 COM R17 OUT PORTB,R17 L: RJMP L
もちろん、ここでは、周波数が同じでなく、パルスが他の3つの結論に達するという事実に固執することができますが、サイズにおいてこれは疑いの余地のない記録です。 おめでとうございます!
コントローラーの安楽死と併せて、ウォッチドッグタイマーは省エネのための強力なツールです。 そして、それを使用すると、最も深い睡眠-パワーダウンを使用できます。 追加の4バイトのコストで、数マイクロワット節約できます。
LDI R16,(1<<WDE) | (1<<WDP2) | (1<<WDP1) OUT WDTCR,R16 ldi r16,(1<<SM1) | (1<<SE) out mcucr,r16 OUT DDRB,R16 COM R17 OUT PORTB,R17 SLEEP
そして、はい、あなたは完全に豚のように行動することができます-10バイトの決定を受けて、記録から最後のループを取り除きます。
UPD:コメントには、 12バイトのレコードソリューションへのリンクがありました。 ブラボー!
UPD2:コントローラーに対する暴力を通して、私はもう2バイト勝ちました。
UPD3:そして、コントローラーに対する暴力をさらに増やした別のソリューション 。
UPD4: 別のオプション -オプション2および3のように、1つの命令で(ただし、すべてのプログラムメモリが同時に実行されます)。