この Web ページは、 Smart Zynq boardの機能を探求する小規模プロジェクトのグループに属しています。
序章
市場には、 SG90、 MG90S、 MG995 、 MG996Rなど、シンプルで低コストの DC サーボ モーターがいくつかあります。これらのモーターは、特にシンプルなロボットの構築など、趣味のプロジェクト向けです。このようなモーターは、単一の PWM signalによって制御されます。
一部のモーターは、限られた角度範囲内 (通常は 180 度) で回転します。このようなモーターの場合、 PWM signal はモーターの角度位置を制御します。他のモーター (「360 度モーター」と呼ばれることが多い) は連続的に回転します。このようなモーターの場合、 PWM signal は回転の速度と方向を制御します。モーター モデルは、多くの場合、次の 2 つのバリエーションで利用できることに注意してください。 1 つは角度が制限されたタイプ、もう 1 つは連続的に回転できるタイプです。この種のモーターを購入するときは、この違いに注意することが重要です。
このチュートリアルでは、最大 8 個のサーボ モーターを Smart Zynq board に接続し、シンプルな Linux commandを使用してこれらのモーターを制御する方法を説明します。また、このチュートリアルでは、 FPGA内に register インターフェイスを実装するために Xillybusの seekable streams を使用する方法も説明します。
このチュートリアルで示す電気配線は、説明と写真をシンプルにするために、1 つのモーターにのみ適しています。モーターを追加するには、ここに示すように、同じコンポーネントに配線を追加するだけです。
モーターの電気インターフェース
該当するタイプのサーボモーターへの接続は、3 本のワイヤで構成されます。
- 茶色のワイヤーはアースに接続されています。
- 赤いワイヤー、 +5V 電源に接続されています。
- オレンジ色のワイヤーは制御信号(PWM)に接続されています。
PWM signal には、 20ms (50 Hz) の周期が必要です。 pulse がハイである期間によって、モーターの位置または角速度が制御されます。
ほとんどの datasheetsによると、 pulseの持続時間は 1ms から 2msの間であるはずです。この情報は、一部のモーターでは正しくありません。たとえば、 Tower Proの SG90 (180 度の回転制限がある) の正しい範囲は、およそ 500μs から 2450μsの間です。この範囲は 180 度の回転に相当します。 pulse を最大 2560μsにすると、モーターをもう少し回転させることができます。このモーターは、これより長い pulse を無視します。
したがって、モーターが応答する pulse 持続時間の範囲を調べるには、 datasheetに頼るのではなく、モーターを実際に試す方がよいでしょう。これらのモーターは趣味のプロジェクト用であるため、仕様データは正確ではない可能性があります。
電気接続
まず、サーボ モーターには、機械モーターにエネルギーを供給するための別の電源が必要です。この電源の電圧は +5Vです。モーターは電圧レベルに突然の変化を引き起こすことが多いため、 Smart Zynq boardに電力を供給する電源を使用することはお勧めしません。電源電圧が不安定な場合、 Smart Zynq board の動作が不安定になる可能性があります。
この信号がハイのとき、オレンジ色のワイヤの PWM signal は電源とほぼ同じ電圧になります。つまり、オレンジ色のワイヤと ground 間の電圧は 0 またはおよそ +5Vになります。
ただし、 Smart Zynq boardの output voltage は、高い場合のみ 3.3V になります。このような output がモーターのオレンジ色のワイヤに直接接続されている場合、モーターが正しく応答する可能性が高くなります。その理由は、 5V 電源に基づくデジタル回路では、通常、 2.5V を超える電圧はすべて logic '1'とみなされるためです。
とはいえ、 PWM signalで 3.3V 電圧を使用してもモーターが確実に動作するかどうかは不明です。推奨される解決策は、 Smart Zynq boardを 0V または 5Vに変換する voltage level shifter を使用することです。このチュートリアルでは、 TXS0108E chip を搭載した小型の board を使用します。この board は、 chipの pinsを露出させるだけなので、通常のワイヤを chipに接続できます。
この図は、1 つのサーボ モーターを制御するために電気接続がどのように配置されているかを示しています。 写真の下部にある Smart Zynq board は、 Dupont wiresを介して TXS0108E Board に接続されています。この board の反対側は、サーボ モーターの PWM input と、モーターの個別の電源 (標準電源プラグのアダプタ経由) に接続されています。モーターの赤と茶色のワイヤもこの boardにはんだ付けされており、モーターに電源電圧を供給しています。
この写真は、 TXS0108E Board とその接続を詳しく示しています。
OE と VA間の赤いワイヤーに注意してください。これにより、 chipの output enable がハイになります。
Smart Zynqの pin header への接続は次の図のようになります。ここに接続された J6/1 によってモーターが制御されます。
この表は、関係する 4 つの部分間の接続をまとめたものです。
TXS0108E Board pin | Smart Zynq pin | モーターのワイヤー | +5V 電源 | コメント |
---|---|---|---|---|
VA | J6/37 (3.3V) | -- | -- | OEにも接続 |
A4 | J6/1 | -- | -- | |
OE | -- | -- | -- | VAに接続 |
GND | J6/35 (GND) | ブラウン wire | GND | |
VB | -- | レッド wire | +5V | |
B4 | -- | オレンジ wire | -- |
TXS0108E chip は、 A4 input から B4 outputまで voltage level shift を実行します。 chipには、さらに 7 組の pins があります。このチュートリアルでは、 A4/B4 ペアが任意に選択されました。8 個のモーターを制御するには、この chip の 8 組の pin ペアをすべて使用できます。8 個のモーターを制御するために必要な配線の数が多く、画像がわかりにくくなるため、ここでは示していません。
J6 pin headerを見つけるには、 Smart Zynq boardの裏側に「Bank 33 VCCIO Vadj」と書かれている場所を探します。このマークに近い pins の列が、このチュートリアルで使用されている pin header です。したがって、 J6/1 は、 HDMI コネクタに最も近い pin です。
Smart Zynq board は、 USB portsの 1 つを介して別の電源に接続されていることに注意してください。また、 J6 pin headerの最後の pin は 5Vであるため、この pinにはワイヤを接続しないでください。
Vivado プロジェクトの準備
demo bundleの zip ファイル ( boot partition kit) から新しい Vivado プロジェクトを作成します。テキスト エディターで verilog/src/xillydemo.v を開きます。 code の「PART 4」というラベルの付いた部分を削除します。その部分の代わりに、次のコードを挿入します。
assign user_r_mem_8_empty = 0;
assign user_r_mem_8_eof = 0;
assign user_w_mem_8_full = 0;
reg [7:0] reg0, reg1, reg2, reg3, reg4, reg5, reg6, reg7;
always @(posedge bus_clk)
if (user_w_mem_8_wren)
case (user_mem_8_addr[2:0])
0: reg0 <= user_w_mem_8_data;
1: reg1 <= user_w_mem_8_data;
2: reg2 <= user_w_mem_8_data;
3: reg3 <= user_w_mem_8_data;
4: reg4 <= user_w_mem_8_data;
5: reg5 <= user_w_mem_8_data;
6: reg6 <= user_w_mem_8_data;
7: reg7 <= user_w_mem_8_data;
endcase
reg [7:0] mem_8_data_reg;
assign user_r_mem_8_data = mem_8_data_reg;
always @(posedge bus_clk)
if (user_r_mem_8_rden)
case (user_mem_8_addr[2:0])
0: mem_8_data_reg <= reg0;
1: mem_8_data_reg <= reg1;
2: mem_8_data_reg <= reg2;
3: mem_8_data_reg <= reg3;
4: mem_8_data_reg <= reg4;
5: mem_8_data_reg <= reg5;
6: mem_8_data_reg <= reg6;
7: mem_8_data_reg <= reg7;
endcase
servo_pwm servo_pwm_i [7:0]
(
.clk(bus_clk),
.rst(quiesce),
.pwm_signal(J6[7:0]),
.pwm_width( { reg7, reg6, reg5, reg4, reg3, reg2, reg1, reg0 } )
);
このリンクから更新された xillydemo.v をダウンロードすることもできます。
プロジェクトの verilog/src/ directoryに、次の内容を含む新しいファイル servo_pwm.v を作成します ( copy-paste を使用するか、 このリンクからダウンロードします)。
module servo_pwm
(
input clk,
input rst,
output reg pwm_signal,
input [7:0] pwm_width
);
reg [9:0] enable_count;
reg enable;
reg [10:0] pwm_count;
reg [8:0] threshold;
always @(posedge clk)
begin
if (enable_count == 999)
begin
enable_count <= 0;
enable <= 1;
end
else
begin
enable_count <= enable_count + 1;
enable <= 0;
end
threshold <= pwm_width + 50; // Add 0.5 ms to PWM width
if (enable)
begin
if (pwm_count == 1999)
pwm_count <= 0;
else
pwm_count <= pwm_count + 1;
pwm_signal <= (pwm_count < threshold);
end
if (rst)
begin
enable_count <= 0;
pwm_count <= 0;
pwm_signal <= 0;
end
end
endmodule
次に、ファイルを Vivado プロジェクトに追加します。 File > Add Sources… をクリックし、「Add or create design sources」を選択します。次に、 Nextをクリックします。 "Add Files" button をクリックし、 verilog/src/ directoryで「servo_pwm.v」という名前のファイルを選択します。次に、 "Finish" buttonをクリックします。
demo bundleの bitstream ファイルを作成したのと同じ方法で、更新されたプロジェクトから bitstream ファイルを作成します。 bitstream ファイルも同様に TF card にコピーします (古い xillydemo.bit ファイルをこのプロジェクトで作成したファイルで上書きします)。
サーボモーターの制御
このセクションに示す手順は、 Smart Zynq board上の shell prompt で実行する必要があります。
まず、 directory を変更し、 Xillybusのデモ アプリケーションの compilation を実行します。
# cd ~/xillybus/demoapps/ # make gcc -g -Wall -O3 memwrite.c -o memwrite gcc -g -Wall -O3 memread.c -o memread gcc -g -Wall -O3 streamread.c -o streamread gcc -g -Wall -O3 streamwrite.c -o streamwrite gcc -g -Wall -O3 -pthread fifo.c -o fifo
FPGA には、サーボ モーターを制御するための registers が 8 つあります。初期値はすべてゼロです。これらの値を 10 進数形式で印刷する方法は次のとおりです。
# hexdump -v -n 8 -e '8/1 "%u " "\n" ' /dev/xillybus_mem_8 0 0 0 0 0 0 0 0
より単純な hexdump command を使用すると、同じ値を 16 進形式で出力できます。
# hexdump -v -n 8 -C /dev/xillybus_mem_8 00000000 00 00 00 00 00 00 00 00 |........| 00000008
memwrite program ( xillybus/demoapps/ directory内) はモーターの制御に使用できます。たとえば、この command は register 0 の値を 120に変更します。
# ./memwrite /dev/xillybus_mem_8 0 120
これにより、 J6/1 に接続されているモーターの回転位置または速度が変更されます (上の写真に示すように)。
PWM pulse の幅は、次の式に従ってこの register の値によって決まります。
t = 500 + (x * 10)
この式では、 t はマイクロ秒単位で指定され、 x は関連する registerの値です。したがって、 pulse のデフォルトの幅は 500μs (つまり、 x=0の場合) です。上記の command により、 register の値が 120に変更されました。その結果、 pulseの幅は 1700μsに変更されました。
各 register は 1 つの byteで構成されているため、その値の範囲は 0 から 255です。したがって、各モーターの pulse は、 500μs から 3050μsまでの幅を持つように変更できます。この値の範囲の一部は、ほとんどのモーターでは無効と見なされることに注意してください。各モーターでさまざまな可能性を試して、どのように反応するかを確認することをお勧めします。
別の pinに接続されているモーターを制御するには、別の registerに書き込みます。たとえば、モーターが Smart Zynqの J6/4に接続されている場合は、この command を使用できます。例:
# ./memwrite /dev/xillybus_mem_8 3 50
この command は、 pulse の幅を 1000μs (つまり 1 ms) に変更します。
これら 2 つの commandsの後、値を読み戻すことで変更を確認できます。
# hexdump -v -n 8 -e '8/1 "%u " "\n" ' /dev/xillybus_mem_8 120 0 0 50 0 0 0 0
値は 10 進数形式で表示されます。16 進数形式の場合:
# hexdump -v -n 8 -C /dev/xillybus_mem_8 00000000 78 00 00 32 00 00 00 00 |x..2....| 00000008
memwrite の仕組み
memwrite の source code は、 ~/xillybus/demoapps/ directoryの memwrite.c という名前のファイルです。この program は、 Xillybusの seekable streamsを利用して registers にアクセスする方法を示しています。この program がどのように機能するかを理解したい場合は、 source codeを参照することをお勧めします。以下では、重要な 2 つの部分についてのみ説明します。
program は、最初の argumentで指定された名前のファイルを開きます。このファイルの file descriptor は、 variable @fdに格納されます。
次に、 program は次のように lseek() への関数呼び出しを実行します。
if (lseek(fd, address, SEEK_SET) < 0) {
perror("Failed to seek");
exit(1);
}
variable @address には、 programの 2 番目の argumentが含まれています。上記の memwrite を使用する最初の例では、これは 0でした。2 番目の例では、これは 3でした。一般的に、 lseek() はファイル内の特定の位置に移動するために使用されます。この場合、ファイル内の位置は、アクセスする register の番号と同じです。
次に、 program は allwrite()に関数呼び出しを行います。
allwrite(fd, &data, 1);
これは、ファイルに 1 つの byte を書き込みます。 allwrite() は memwrite.cで定義されており、よく知られている function write()に似ています。したがって、 allwrite()を使用する代わりに、これはほぼ同じになります。
write(fd, &data, 1);
違いは、 write() はデータが書き込まれることを保証しない点です。一方、関数 allwrite()は、データがファイルに書き込まれることを保証します。
この場合、 allwrite() への関数呼び出しは、 programの 3 番目の argumentの値を書き込みます。つまり、これは registerの新しい値です。
host 側の Xillybusの API の詳細については、 この主題に関するドキュメント、特にセクション 6.1を参照してください。
Verilog codeの説明
まず、 xillydemo.v の Verilog code が FPGA内で registers を実装する方法を示します。その後、 PWM pulseの実装について説明します。
Xillybusの API でハードウェア registers を実装するための詳細は、 Xillybus FPGA designer's guideで説明されています。
registers に関連する wires は、元の xillydemo.v ファイルですでに定義されています。
// Wires related to /dev/xillybus_mem_8
wire user_r_mem_8_rden;
wire user_r_mem_8_empty;
wire [7:0] user_r_mem_8_data;
wire user_r_mem_8_eof;
wire user_r_mem_8_open;
wire user_w_mem_8_wren;
wire user_w_mem_8_full;
wire [7:0] user_w_mem_8_data;
wire user_w_mem_8_open;
wire [4:0] user_mem_8_addr;
wire user_mem_8_addr_update;
これらの wires は、 instantiationの一部として Xillybus IP core に接続されます。
xillybus xillybus_ins (
// Ports related to /dev/xillybus_mem_8
// FPGA to CPU signals:
.user_r_mem_8_rden(user_r_mem_8_rden),
.user_r_mem_8_empty(user_r_mem_8_empty),
.user_r_mem_8_data(user_r_mem_8_data),
.user_r_mem_8_eof(user_r_mem_8_eof),
.user_r_mem_8_open(user_r_mem_8_open),
// CPU to FPGA signals:
.user_w_mem_8_wren(user_w_mem_8_wren),
.user_w_mem_8_full(user_w_mem_8_full),
.user_w_mem_8_data(user_w_mem_8_data),
.user_w_mem_8_open(user_w_mem_8_open),
// Address signals:
.user_mem_8_addr(user_mem_8_addr),
.user_mem_8_addr_update(user_mem_8_addr_update),
[ ... ]
.quiesce(quiesce)
);
次に、このプロジェクト専用に追加された Verilog code を見てみましょう。この部分から始まり、 hostとのデータ交換に flow control が適用されないようにします。
assign user_r_mem_8_empty = 0;
assign user_r_mem_8_eof = 0;
assign user_w_mem_8_full = 0;
8 つの registers は次のように宣言されます。
reg [7:0] reg0, reg1, reg2, reg3, reg4, reg5, reg6, reg7;
次の部分では、 registersへの書き込み操作を実装します。 @user_w_mem_8_wren がハイの場合、 registersの 1 つに新しい値が書き込まれます。 @user_mem_8_addr は、影響を受ける register を選択します。 @user_w_mem_8_wren と @user_mem_8_addr はどちらも Xillybus IP coreの outputs です。
always @(posedge bus_clk)
if (user_w_mem_8_wren)
case (user_mem_8_addr[2:0])
0: reg0 <= user_w_mem_8_data;
1: reg1 <= user_w_mem_8_data;
2: reg2 <= user_w_mem_8_data;
3: reg3 <= user_w_mem_8_data;
4: reg4 <= user_w_mem_8_data;
5: reg5 <= user_w_mem_8_data;
6: reg6 <= user_w_mem_8_data;
7: reg7 <= user_w_mem_8_data;
endcase
この後、これらの registers から値を読み取る機能が実装されます。 @user_r_mem_8_rden がハイの場合、 @user_r_mem_8_data が更新され、 registersのいずれかの値が含まれます。 @user_mem_8_addr は、どの registerの値を読み取るかを選択します。
reg [7:0] mem_8_data_reg;
assign user_r_mem_8_data = mem_8_data_reg;
always @(posedge bus_clk)
if (user_r_mem_8_rden)
case (user_mem_8_addr[2:0])
0: mem_8_data_reg <= reg0;
1: mem_8_data_reg <= reg1;
2: mem_8_data_reg <= reg2;
3: mem_8_data_reg <= reg3;
4: mem_8_data_reg <= reg4;
5: mem_8_data_reg <= reg5;
6: mem_8_data_reg <= reg6;
7: mem_8_data_reg <= reg7;
endcase
最後に、 servo_pwm module の instantiation が登場します。
servo_pwm servo_pwm_i [7:0]
(
.clk(bus_clk),
.rst(quiesce),
.pwm_signal(J6[7:0]),
.pwm_width( { reg7, reg6, reg5, reg4, reg3, reg2, reg1, reg0 } )
);
この instantiation は、 servo_pwmの同一のコピーを 8 つ作成することに注意してください。 最初のコピーは reg0 と J6[0]に接続され、2 番目のコピーは reg1 と J6[1] などに接続されます。
次に、 servo_pwm moduleを見てみましょう。まず、 moduleの ports と registersを宣言します。
module servo_pwm
(
input clk,
input rst,
output reg pwm_signal,
input [7:0] pwm_width
);
reg [9:0] enable_count;
reg enable;
reg [10:0] pwm_count;
reg [8:0] threshold;
前述のように、 @pwm_signal は registerの値を受信し、この値は hostの memwrite command の助けを借りて更新されます。 @pwm_signal は Smart Zynq boardの pinに接続されているため、これがオレンジ色のワイヤでモーターに届く信号です。
この module の次の部分は strobe signalを実装します。 @enable は、1000 回の clock cyclesごとに 1 回ハイになります。 @clkの frequency は 100 MHzです。したがって、 @enable は、 10μsの時間間隔ごとに 1 回ハイになります。
always @(posedge clk)
begin
if (enable_count == 999)
begin
enable_count <= 0;
enable <= 1;
end
else
begin
enable_count <= enable_count + 1;
enable <= 0;
end
前述のように、 registerの値の範囲 0 から 255 は、 500μs から 3050μs の間の PWM pulse 幅に対応します ( pulseの幅のマイクロ秒単位の expression は 500 + x * 10でした)。
したがって、これは @thresholdの expression です。
threshold <= pwm_width + 50; // Add 0.5 ms to PWM width
この register には、 PWM pulse 中に @enable がハイになる回数 (つまり、 PWM signaがハイのとき) が含まれます。この expression は、 register の値が 10μs単位の PWM pulse の長さであるという事実を反映しています。 registerの値に 50 を追加すると、 500μsの最小 pulse 幅に相当します。
この部品では、@enable が clock enable として使用されるようになりました。
if (enable)
begin
if (pwm_count == 1999)
pwm_count <= 0;
else
pwm_count <= pwm_count + 1;
pwm_signal <= (pwm_count < threshold);
end
@pwm_count は 0 から 1999 までカウントし、再び最初から開始します。この counter は 10μsごとに 1 回だけ変化するため、 20000μs = 20 msごとに 1 ラウンド完了します。つまり、 PWM pulseの繰り返しサイクルは、必要に応じて 20msになります。
@pwm_count が @thresholdより小さい限り、@pwm_signal は高くなります。このようにして、 pulseの幅は registerによって制御されます。
この module の最後の部分は、いくつかの registersをリセットすることです。
if (rst)
begin
enable_count <= 0;
pwm_count <= 0;
pwm_signal <= 0;
end
end
endmodule
@rst がハイの場合、この部分は上記の記述をすべて無効にします。したがって、 @rst は reset signalの機能を持ちます。
Verilog code と実際の pinsの関係
上記の Verilog code は J6という名の inout port を使用していますが、この port との接続はどのようにして pin headerに到達するのでしょうか。答えは xillydemo.xdcにあります。このファイルは、 bitstream ( "vivado-essentials" directory内) を作成する Vivado プロジェクトの一部です。
xillydemo.xdc には、 FPGA が電子部品として正常に動作するために必要なさまざまな情報が含まれています。特に、このファイルには次の行が含まれています。
[ ... ]
## J6 on board (BANK33 VADJ)
set_property PACKAGE_PIN U22 [get_ports {J6[0]}]; #J6/1 = IO_B33_LN2
set_property PACKAGE_PIN T22 [get_ports {J6[1]}]; #J6/2 = IO_B33_LP2
set_property PACKAGE_PIN W22 [get_ports {J6[2]}]; #J6/3 = IO_B33_LN3
set_property PACKAGE_PIN V22 [get_ports {J6[3]}]; #J6/4 = IO_B33_LP3
set_property PACKAGE_PIN Y21 [get_ports {J6[4]}]; #J6/5 = IO_B33_LN9
set_property PACKAGE_PIN Y20 [get_ports {J6[5]}]; #J6/6 = IO_B33_LP9
set_property PACKAGE_PIN AB22 [get_ports {J6[6]}]; #J6/7 = IO_B33_LN7
set_property PACKAGE_PIN AA22 [get_ports {J6[7]}]; #J6/8 = IO_B33_LP7
[ ... ]
最初の行は、 port J6[0] を U22に接続する必要があることを示しています。これは、 FPGAの物理パッケージ上の位置です。 Smart Zynqの schematicsによると、この FPGA pin は pin headerの最初の pin に接続されています。他の ports の位置も同様に定義されています。
概要
このチュートリアルでは、 Smart Zynq board および Xillybusの register APIを使用してサーボ モーターを制御する方法を説明しました。このアプリケーション用の host application と Verilog code も紹介されました。
このチュートリアルは、 registersを利用してハードウェアを制御する必要がある他のアプリケーションの基礎としても役立ちます。