警告: この記事ではダーティハックがよく使用されます。 それは、「どうしてはいけない」手当としてのみとることができます!
「小さなマイクロコントローラー用の小さなハローワールド-24バイト」という記事を見るとすぐに、私の内部アセンブラーは「そのような貴重なバイトをばらまくのは本当に可能でしょうか?!」 そして、私はずっと前にCに切り替えましたが、コンパイラのバイパスコードをチェックすることで重要な場所に干渉することはありません。すべてが悪い場合は、Cコードをわずかに変更して速度やスペースを大幅に向上させることができます。 または、この部分をアセンブラで書き直してください。
したがって、タスクの条件:
- AVRマイクロコントローラー、私はビンで最もATMega48を持っていました。
- 内部ソースからのクロッキング。 実際には、外向きに任意の低い周波数でAVRをクロックすることができます。これにより、私たちのタスクはすぐにスポーツマンらしくないカテゴリに分類されます。
- 目に見える周波数でLEDを点滅させます。
- プログラムのサイズは最小限に抑える必要があります。
- マイクロコントローラの卓越したパワーはすべてタスクに急ぎます。
表示のために、V CC電源バスと小さなメガのB7ピンの間にLEDを抵抗器で接続します。
AVR Studioで書き込みます。
asmの荒野に突入しないために、Cで最初の明らかな擬似コードを示します。
int main(void) { volatile uint16_t x; while (1) { // while (++x) // ; DDRB ^= (1 << PB7); // B7 } }
他のタスクに気を取られる必要がないため、タイマーの使用は明らかに冗長です。 GCCの通常の遅延関数_delay_us()は、ここで与えられている内部whileループに似たものに基づいています。 変数xをすぐに不適切に処理しました-オーバーフローに基づいてサイクルを作成しますが、実際の問題では受け入れられません。
私たちはリストを見て、コンパイラの無駄に怖がっており、アセンブラに基づいてプロジェクトを作成しています。 コンパイラによってコンパイルされたものから余分なものを捨てますが、それは残ります:
.include "m48def.inc" ; ATMega48 .CSEG ; ldi r16, 0x80 ; r16 = 0x80 start: adiw x, 1 ; [r26:r27] 1 brcc start ; , in r28, DDRB ; r28 = DDRB eor r28, r16 ; r28 ^= r16 out DDRB, r28 ; DDRB = r28 rjmp start ; goto start
リセットを使用するとアドレス0x0000に到達するため、割り込みを使用しない場合は、テーブルの代わりにコードを直接配置します。 xが0xFFFFから0x0000に移動すると、キャリー(オーバーフロー)フラグCとゼロ結果フラグZが設定され、brneまたはbrccを使用してすべてをキャッチできます。
14バイトのマシンコードと、カウンターサイクル時間= 4クロックサイクルを取得しました。 xは2バイトであるため、LEDの点滅の半周期は65536 * 4 = 262144クロックサイクルです。 内部タイマー、つまり128kHz RCオシレーターをもっとゆっくり選択してみましょう。 半サイクルは262144/128000 = 2.048秒です。 タスクの条件は満たされていますが、ファームウェアのサイズは明らかに削減できます。
まず、DDRBポートの方向のステータスの読み取りを犠牲にします。なぜそれが必要なのか、常に0x00または0x80のいずれかがあることを既に知っています。 はい、そうするのは良くありませんが、ここではすべてが制御されています! 次に、ポートBの他の結論は使用されません。そこでゴミが記録されていても大丈夫です。
変数xの上位に注意しましょう。65536/ 2 * 4 = 131072クロックサイクル後に厳密に変化します。 それでは、上位バイトxhをポートに出力して、内部ループとr16変数を削除しましょう。
start: adiw x, 1 ; [r26:r27] 1 out DDRB, xh ; DDRB = r27 rjmp start ; goto start
いいね! 6バイトヒットします! タイミングを計算します:(2 + 1 + 2)* 65536/2 =163840。これは、LEDが半周期163840/128000 = 1.28秒で点滅することを意味します。 ポートBの残りの脚ははるかに速く痙攣します。これに目を留めるだけです。
そして、これを落ち着かせることができますが、実際のアセンブラーは袖に以前のすべてのものを組み合わせたよりもさらに汚いトリックを持っています! プログラムの3分の1を占める(だけを考えて)このrjmpを捨ててみませんか?! 奥に向ける。 マイクロコントローラのフラッシュメモリを消去した後、すべてのセルの値は0xFFになります。つまり、プロセッサがプログラムの制限を超えた後、0xFFFF命令のみに遭遇し、文書化されていませんが、0x0000(nop)のように実行されます。つまり、プロセッサは実行されません。実行可能な命令(プログラムカウンタ)のレジスタポインタを増やすだけです。 制限値に達した後、この場合はプログラムメモリサイズ4096-1 = 4097であり、オーバーフローして再び0に等しくなり、プログラムの開始を示します。 ここで、遅延はプログラムメモリ全体の通過によって決定されます。これらは1クロックサイクルで実行される2048個の2バイト命令です。 したがって、1バイトのカウンター変数を使用します。
inc r16 ; r16++ out DDRB, r16 ; DDRB = r16
またはCで:
uint_8 b DDRB = ++b;
LEDの点滅のハーフタイムは、2048 * 256/2 = 262144サイクルまたは2.048秒です(最初の例のように)。
合計で、プログラムのサイズは4バイトですが 、機能的ですが、この勝利は、鏡を見るのを恥ずかしいほどの価格で達成されました。 ところで、コンパイルオプション-Os(高速でコンパクトなコード)を使用すると、元のCプログラムのサイズは110バイトでした。
結論
あなたが言語内でfeel屈に感じた場合-一番下に行くと、複雑なことは何もありません。 プロセッサの動作方法を学習すると、トップレベル言語を使用することでプロセッサがはるかに簡単になります。 はい、抽象化が流行しています:フレームワーク、コーヒーメーカーのLinux、組み込みのx86でも、アセンブラーは、ハードリアルタイム、最大パフォーマンス、限られたリソースなどが必要な場合に位置を放棄しません。家族内でも)、変更可能性、何が起こっているのか理解しにくく、大きなプログラムを書くことの難しさ、アセンブラー、小さな関数と挿入は非常にうまく書かれており、このニッチから決して抜け出せないようです! これは主に組み込み機器に適用されますが、ほとんどのx86プログラマーの生活の中で、アセンブラーは主にデバッグ中に発生し、恐ろしいリストを表示します。
私にとって、ASM対Cは存在せず、それらを一緒に使用し、Cが大幅に普及しています。
剣の使用は細心の注意を必要とします。
ご清聴ありがとうございました!
UPD1
怠zyではない、鉄に注がれた-はい、それは動作します!
UPD2
しかし、絶対にしないでください!
プログラムをさらに切断するという考えが私たちの心を離れないという事実を考慮して、私たちは続けます。
私は自分で試したことはありませんが、インターネット上の一部の人々は、PINxレジスタに書き込むと、PORTxの値は反対に変化すると言います(最も古いAVRマイクロコントローラーを除く)。 これは、V CCと出力の間で内部プルアップ抵抗が接続/切断されることを意味します。
LEDを低電流に敏感にし、端子B0とグランドの間に接続します。
ヒューズCKDIV8をプログラムすると、クロック周波数がさらに8倍低下します(最大16 kHz)。 (たった今、すべてのプログラマーがマイクロコントローラー、たとえば元のAVRISP mkIIを再プログラムできるわけではありません-おそらく、そのクローンを保証することはできません)。
プログラムを1コマンド ( 2バイト )にしましょう:
sbi PINB, 0 ; PINB = 0x01 PORTB ^= 0x01
点滅し、暗闇の中でちらつきが観察されます。 周波数16000/2049/2≈4 Hz 大量のフラッシュメモリを備えたマイクロコントローラーの場合、この周波数はそれに応じて低くなります-このような点滅まで。
UPD3
先に進みます。
AVRマイクロコントローラーは、プログラムなしで作業を通知できますか?
もちろん! ヒューズCKOUTをプログラムするだけで十分です。その後、CLKOピン(再びPB0)が内部信号を含むクロック信号を生成し、その周波数がプリセレクターによって低下すると、遅延信号が出力されます。
したがって、クリスタルを消去し、プログラムを0バイトに書き込まず、ヒューズをフラッシュします。 しかし、抵抗器付きのLEDに16 kHzを適用してもほとんど意味がありませんが、半分の明るさで点灯していることがわかります。
ただし、視覚的な低周波Hello Worldのほかに、高周波音声があります! もちろん、このオプションは最初のTKには対応していませんが、MKの作業を完全に示しています。 端子B0とグランドまたは電源バスの間の圧電素子をつかみ、厄介なきしみを「楽しんで」います。