ポートの直接操作の事例
プログラミングが大勢の人にますます身近なものになるにつれ、基礎的かつプリミティブながらも利用価値あるビットコードの中には使われなくなったり、あるいは完全に忘れ去られたりするものもあるでしょう。ポートの直接操作(DPM)などがその例です。一部のプログラマは、このようなコマンドは読みづらく、初心者には混乱の元にもなるため、馴染んだプログラム構造に混ぜ込ませるべきではないと主張します。しかし、コードを簡潔で効率の良いものに保つためには、使えるあらゆる手段を利用してもよいのではないでしょうか。
たとえば、8ビットマイクロコントローラのポートの動作を制御するには、ポートごとに3つの8ビットレジスタが必要です。それらのレジスタとは、入出力の方向指定用のDDRx、ピンの論理レベル制御用のPORTx、ポートピンの現在の状態を保持するPINxです。詳しく説明するため、ポートCに注目しましょう。設定処理の中で、プログラマがポートCのピンを出力用とし、初期状態をLOWにしたいとします。多くの場合、3~4行のコードを含んだ簡潔な「forループ」文を記述し、これを必要なピン0~ピン7について順次繰り返し、計32行分のコードを実行するようにします。ループ内でpinMode()やdigitalWrite()などのライブラリ関数を使うと、さらに「舞台裏」での処理が行われるため、多くのコードが別途実行されることになります。
最終的には、ポートC用方向レジスタ(DDRC)の8つのビットがHIGHに、ポートC用論理レベルレジスタ(PORTC)の8つのビットがLOWに設定されます。まったく同じ処理を、以下に示すポートレジスタに対する直接コマンドで実現できます。
DDRC = 0xFF; //ポートCのピンを出力に設定(2進表記で、DDRD = 0b11111111;)
PORTC = 0x00; //ポートCのピンをLOWに設定(2進表記で、PORTC = 0b00000000;)
ポートAを入力に設定するには以下のようにします。
DDRA = 0x00; //ポートAのピンを入力に設定(2進表記で、DDRA = 0b00000000;)
最後に、ポートDを分割して入力および出力に設定します。
DDRD = 0x0F; //ポートDの上位4ビットを入力に、残りの下位ビットを出力に設定(2進表記で、DDRD = 0b00001111;)
0xFFのように16進値を使うと、レジスタへのビット値の割り当てについて興味深いことに気が付きます。「0x」は16進表記であることを示し、1番目の数である「F」は8ビットレジスタの上位4ビットを表し、2番目の数である「F」は下位4ビットを表します。
HIGHかLOWに設定するビットのあらゆる組み合わせは、16進または2進のリテラル(数)で表現できます。2進リテラルを使うと、記述したコードのままで8つの各ビットを表現でき、より視覚的になります。コンパイラがサポートしていればどちらでも構いませんが、16進の方が短く、見た目はすっきりします。
注:2進リテラルの使用は、C/C++のすべてに共通の規格ではありません。
ワンステップさらに進んだ構成
設定処理でポートAおよびポートCを構成すると、メインループにDPMを使ったわずか数行のコードを記述するだけで、デジタルデバイスの読み取りおよび他のデジタルデバイスの操作ができます。実際、デジタルジョイスティックで制御するトラベルリミットスイッチ付きステッピングモータ2基を最小限のレジスタコマンドとルックアップテーブルを使って容易にプログラミングできます。その場合のハードウェア構成を図1に示します。
図1:マイクロコントローラのポートAおよびポートCを中心としたジョイスティック/ステッピングモータのハードウェア構成(画像提供:DigiKey)
ハードウェア:
VCCに接続したノーマリオープン(NO)の接点を4つ備えた単一のデジタルジョイスティックがポートAのピン0~ビン3にも接続されています。出力はマイクロコントローラでLOWに設定されています。接点が閉じると、ポートピンがHIGHになります。各接点は、UP、DOWN、LEFT、RIGHTの制御を表します。隣り合った2つの接点は同時に閉じることができるように構成されているため、スイッチ出力の組み合わせは8通りになります。9番目の出力は、ジョイスティックが中央の位置にあってすべての接点が開いている場合である「停止」を表します。
グランドに接続されたNOの接点を備えた4つのリミットスイッチは、ジョイスティックのUP、DOWN、LEFT、RIGHTの位置に対応するポートAの残りのピンにも接続されています。スイッチ出力は、マイクロコントローラでHIGHに設定されています。接点が閉じると、ピンがLOWになります。
ステッピングモータドライバボードは、3つの制御ピンをHIGHとLOWに切り換えてステップ、方向、ホールドの機能を実行することで機械的なモーションを作りだします。この例では、ポートCの8つのビットすべてが2つのドライバの操作用ですが、2つのピンは使用されません。
プログラミング:
ドライバへの正しい出力を生成するには、ルックアップテーブルを使用してジョイスティック用の4ビットをドライバ用の8ビットに変換します。メインループ内のわずか1行のコードで関数「get_output()」を呼び出し、PINAレジスタの内容をこの関数に渡します。この関数から返された値は直接、PORTCレジスタに書き込まれます。
PORTC = get_output(PINA);
関数内では、ルックアップテーブル「lookup_output[ ]」にアクセスして、インデックスの指す値を返します。しかしこの関数内では、他にリミットスイッチに関して実行していることがあります。「lookup_output[ ]」の角括弧内に入るルックアップテーブルのインデックス値は、ビットのシフトおよびマスクの式で表されています。結果として得られるインデックス値は、入力レジスタ(リミットスイッチ値)の上位4ビットと下位4ビット(ジョイスティック値)のビットごとのAND演算を行ったものです。リミットスイッチの接点のいずれかが閉じていて、上位4ビットのいずれかに0がある場合、対応する下位ビットがクリアされます。
注:4ビットでビットの組み合わせ16通りを表現できるため、ルックアップテーブルのインデックス位置として使われることのない7つは、エラーを防ぐため0x00に変換されます。
コピーuint8_t get_output(uint8_t porta_val) { return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)]; }
例:
すべてのリミットスイッチが開いていてジョイスティックが「UPおよびRIGHT」の位置の場合、PINAレジスタの2進値0b11111001(16進では0xF9)がこの関数に渡されます。この関数は、0b00001111(0x0F)とビットごとのAND演算を行って上位4ビットを0にし、0b00001001(0x09)をルックアップテーブルのインデックス値として得ます。この結果とシフト後のリミットスイッチの状態値0b00001111(0x0F)でもう一度ビットごとのAND演算を行うと元の値に変化はなく、この0b00001001(0x09)を最終的なインデックス値とし、これが指すルックアップテーブルの値0b00100001(0x21)を返します。これが、ステッピングモータドライバによる「UPおよびRIGHT」の場合の変換です(図2を参照)。
図2:ジョイスティックが「UPおよびRIGHT」の位置にある場合の、マイクロコントローラによるポートAおよびポートCの入力の解釈(画像提供:DigiKey)
UPのリミットスイッチが閉じてビットが0になっている場合、シフト後の値は0b00001111(0x0F)ではなく0b00000111(0x07)になり、これが対応するUPのジョイスティック値をクリアするため、最終的なインデックス値は0b00001001(0x09)ではなく、0b00000001(0x01)になります。0b00000001(0x01)の変換値はルックアップテーブルの0x26です。これは、「RIGHTのみ」の場合のステッピングモータドライバによる変換です(図3を参照)。
図3:UPのリミットスイッチが閉じていてジョイスティックが「UPおよびRIGHT」の位置にある場合の、マイクロコントローラによるポートAおよびポートCの入力の解釈(画像提供:DigiKey)
まとめ
ポートデータの設定や読み取り、書き込みに有効な手段としてプログラマがDPMを採用するかどうかに関わらず、大幅にコードを小さくすることは十分な動機になるでしょう。同じ動作に標準ライブラリ関数を使用すると、はるかに多くのコードを要し、利用可能なプログラムメモリのかなりの量を占めてしまうかもしれません。下に示すコードは、ベンチテストで使用されるATMEGA328Pマイクロコントローラのメモリの1%も使用していません。コードに適切な注釈を付けることは、記述したコードのDPM機能を理解するのに大切で、どんなレベルのプログラマにとってもデバッグする際にコード利用を向上させます。
DigiKeyのハードウェア商品の例:
ステッピングモータ - https://www.digikey.com/short/pdnfp4
ステッピングモータコントローラ - https://www.digikey.com/short/pdnf4r
ジョイスティック - https://www.digikey.com/short/pdnf57
リミットスイッチ - https://www.digikey.com/short/pdnfwm
サンプルコード:
コピーconst uint8_t lookup_output[16] = { 0x09, //Index 0 All Stop.Apply hold current 0x26, // Index 1 Right 0x34, // Index 2 Left 0x00, // Index 3 Unused 0x36, // Index 4 Down 0x0C, // Index 5 Down/Right 0x31, // Index 6 Down/Left 0x00, // Index 7 Unused 0x24, // Index 8 Up 0x21, // Index 9 Up/Right 0x0E, // Index 10 Up/Left 0x00, // Index 11 Unused 0x00, // Index 12 Unused 0x00, // Index 13 Unused 0x00, // Index 14 Unused 0x00 // Index 15 Unused }; void setup() { // Set all bits in port A direction register as INPUTs; // Limits (up, down, left, right) Joystick (up, down, left, right) DDRA = 0x00; // Set all bits of port C direction register as OUTPUTs; // Motor control (Not Used, Mot_1, Dir_1, En_1, Not Used, Mot_2, Dir_2, En_2 DDRC = 0xFF; } void loop() { //Send the port A values to the function.Write the return value to port C. PORTC = get_output(PINA); } /***** Input Value Translation Function *******/ uint8_t get_output(uint8_t porta_val) { // Compare the limit switch and joystick values.Retrieve and return the translated value.return lookup_output[(porta_val >> 4) & (porta_val & 0x0F)]; }
Have questions or comments? Continue the conversation on TechForum, Digi-Key's online community and technical resource.
Visit TechForum


