工業製造
産業用モノのインターネット | 工業材料 | 機器のメンテナンスと修理 | 産業プログラミング |
home  MfgRobots >> 工業製造 >  >> Manufacturing Technology >> 製造プロセス

DCモーターを制御するための秘訣

コンポーネントと消耗品

>
Arduino Due
実際には、任意のArduinoボードを使用できます。
× 1
Makeblock Me TFT LCD
これはオプションのコンポーネントです。別のタイプのディスプレイを使用することも、使用しないことを選択することもできます。
× 1
抵抗4.75kオーム
抵抗は、実装によって異なる場合があります。
× 4
ロータリーポテンショメータ(汎用)
3つはオプションです(コントローラーの係数を調整するためだけです)。
× 4
汎用トランジスタPNP
使用するトランジスタは、実装によって異なる場合があります。
× 4
DCモーター(汎用)
× 1
スライドスイッチ
これは方向の選択に使用されます。
× 1
光電速度センサーHC-020K
× 1
ブレッドボード(汎用)
× 1
ジャンパー線(汎用)
× 1

アプリとオンラインサービス

>
Arduino IDE

このプロジェクトについて

PIDコントローラーとPWM出力によるDCモーターの速度と方向の制御

イントロ

利用可能なほとんどすべてのプロジェクトで、オリジネーターはモーターの速度と方向を一緒に制御したいと考えていますが、モーター制御回路を介してでも、主にPWMをDCモーターに直接送信することを好みます。しかし、「摩擦」と「慣性」と呼ばれる双子の兄弟のために、希望どおりの速度に正確に一致させる必要がある場合、このような方法は常に失敗します。

(Twinsのせいにしないでください。何かの有無にかかわらず、何らかの行動を起こしたいときはいつでも、Twinsがすぐに来て行動を起こし、すべてを管理できるようにします。Inertiaは行動を起こす前に物事を「考える」ことができますが、摩擦は加速と速度を制限します。そして、完全に制御されていない場合、「パワー」は「何も」ありません。)

<図>

したがって、入力をPWM信号として出力に送信してモーターの速度を直接制御しようとすると、実際の速度が設定値に達することはなく、すぐ上の画像に示すように、大きな違い(エラー)が発生します。ここで別の方法が必要です。これは「PID制御」と呼ばれます。

PIDコントローラー

PID制御とは何ですか?車の運転方法を想像してみてください。フルストップから移動を開始するには、通常のクルーズ中よりもアクセルペダルを踏み込む必要があります。 (ほぼ)一定の速度で移動している間は、アクセルペダルを踏みすぎる必要はありませんが、必要に応じて速度の低下を回復するだけです。また、加速が必要以上に高い場合は、少し離します。これは「効率的な運転」の方法でもあります。

したがって、PIDコントローラーはまったく同じことを行います。コントローラーは、設定値と実際の出力の差「エラー信号(e)」を読み取ります。 「プロポーショナル」、「インテグラル」、「デリバティブ」と呼ばれる3つの異なるコンポーネントがあります。そのため、コントローラーの名前は、それぞれの最初の文字の後に表示されます。比例成分は、実際のエラー信号に対するコントローラー出力の傾き(加速度)を単純に定義します。積分部分は、最終的なエラーを最小限に抑えるために、時間内にエラー信号を合計します。そして、微分成分はエラー信号の加速を監視し、「調整」を行います。これ以上の詳細はここでは説明しません。興味がある場合は、インターネットでさらに検索してください。

私のArduinoプログラムでは、PIDコントローラーは以下に示す関数として記述されています:

  float controllerPID(float _E、float _Eprev、float _dT、float _Kp、float _Ki、float _Kd){float P、I、D; / *基本式:U =_Kp *(_ E + 0.5 *(1 / _Ki)*(_ E + _Eprev)* _ dT + _Kd *(_ E-_Eprev)/ _ dT); * / P =_Kp * _E; / *比例成分* / I =_Kp * 0.5 * _Ki *(_ E + _Eprev)* _dT; / *積分コンポーネント* / D =_Kp * _Kd *(_ E-_Eprev)/ _dT; / *派生コンポーネント* / return(P + I + D);}  

次に、現在の出力値とPIDコントローラーの出力を加算するだけで最終出力値が決定されます。これは、メインプログラムの次のセクションと、エラー信号およびPIDコントローラー出力の計算です。

  / *エラー信号、モーターへのPIDコントローラー出力および最終出力(PWM)* / E =RPMset --RPM; float cPID =controllerPID(E、Eprev、dT、Kp、Ki、Kd); if( RPMset ==0)OutputRPM =0; else OutputRPM =OutputRPM + cPID; if(OutputRPM <_minRPM)OutputRPM =_minRPM;  
<図>

<図>

DCモーター供給回路

もちろん、Arduinoまたは同様の制御ボードの出力から直接DCモーターを駆動することは決してお勧めしません。 DCモーターは、コントローラーカードの出力から供給できないものと比較して、かなりの量の電流を必要とします。したがって、リレーコイルを駆動する必要があります。しかし、ここで別の問題が発生します。リレーには機械部品があり、中長期的に故障する可能性があります。ここには別のコンポーネント、トランジスタが必要です。

実際、DCモーターは電圧ではなく電流によって駆動されます。そこで、この原理を利用して、トランジスタを使うことにしました。ただし、モーター電流に耐えられる正しいトランジスタを選択する必要があります。まず、電源に接続して直接モーターを運転し、最大動作条件で電流を測定するか、メーカーの仕様を参照してください。

これを行った後、モーターコイルを流れる電流の方向を決定するために「ブリッジ」に4つのBC307A PNPバイポーラ接合トランジスタを使用することにしました(実際には、NPN BC337セットは、大幅に高いコレクタ電流に耐える能力があるため、より適切に機能しますが、私はしませんでした。一度にそれらを持っている)。

モーター電流はトランジスタのエミッタ-コレクタパスを通過する必要があるため、ほぼ同じDC電流ゲイン(hfe)係数を持つトランジスタを使用する必要があります。それをチェックするには、次の回路を使用して、電流計でほぼ同じ電流読み取り値を与えるトランジスタを収集できます。これらの予備回路を設計するには、次のことを考慮する必要があります。

  • Base-EmitterOn-Voltage 」を検索します 」( VBEon )トランジスタの。これは、トランジスタをオンにするためにベースに印加される最小電圧です。
  • 典型的な「 DC電流ゲイン」を見つける 」( hfe )モーター電流に近いコレクタ電流付近のトランジスタ。通常、これはコレクタ電流間の比率です。 ( IC )およびベース電流 IB )、 hfe =IC / IB
  • 最大連続コレクタ電流」を検索します 」のトランジスタ( ICmax )。モーターのDC電流は、絶対値でこの値を超えてはなりません。トランジスタのICmax(abs)=100 mAであるのに対し、使用するモーターは70 mAで必要なので、BC307を使用できます。
<図>

<図>

<図>

これで、ベースに接続する抵抗値を決定できます。最初に、コントローラカードの出力の制限を考慮し、ベース電流を可能な限り最小に保つようにする必要があります(したがって、トランジスタのDC電流ゲインを最大として選択することをお勧めします可能な限り、コントローラボードの出力の定格電圧を「トリガー電圧」とします。 」( VT )、必要なベース電流( IBreq )モーター電流( IM )からDC電流ゲイン( hfe )トランジスタの: IBreq =IM / hfe

次に、抵抗器の両端で降下する電圧を決定します( VR )、 Base-Emitter On-Voltage を差し引くことにより ( VBEon トリガー電圧から : VR =VT-VBEon

最後に、電圧を分圧して抵抗で降下させます。 ( VR )から必要なベース電流 IBreq 抵抗値を見つける ( R ): R =VR / IBreq

[組み合わせた定式化: R =(VT-VBEon)* hfe / IM ]

私の場合:

  • モーター電流:IM =70 mA
  • BC307Aパラメータ:ICmax =100 mA、hfe =140(私は概算で測定)、VBEon =0.62 V
  • トリガー電圧:VT =3.3 V(Arduino DueのPWM出力)
  • R =5360オーム(したがって、2K2と2K7で作られた4900オームを使用して、RPM範囲全体をカバーし、回路がPWM出力から約0.6 mAを吸収するようにすることにしました。これは適切な設計です。)

方向を逆にすることと重要な注意事項

DCモーターの方向を元に戻すには、電流の流れを元に戻すだけで十分です。そのためには、4つのトランジスタセットでブリッジ回路を作成するだけです。回路図について; PWM出力#2はT1AとT1Bをアクティブにし、PWM出力#3はT2AとT2Bをアクティブにするため、モーターを流れる電流が変化します。

ただし、ここでは別の問題を考慮する必要があります。電気モーターは、始動直後に、通常/連続動作中に読み取る公称電流よりも大幅に高い過渡始動電流を吸い込みます(メーカーは公称電流のみを供給します)。起動電流は、小出力モーターの公称電流の約130%になる可能性があり、モーターの出力に応じて増加します。したがって、モーターに電圧源から直接給電し、動作中にすぐに極性を逆にすると、モーターは完全に停止していないため、極端なレベルの電流を吸い込みます。最後に、これにより、電源が切れたり、モーターコイルが焼損したりする可能性があります。これは、非常に小さなパワーモーターではそれほど重要ではなく、感じられるかもしれませんが、作業中のパワーレベルが上がると重要になります。ただし、トランジスタまたはトランジスタのセット(ダーリントンカップルなど)を介してモーターに電力を供給している場合、トランジスタはすでに電流を制限しているため、このような問題は発生しません。

とにかく、私はプログラムの小さなルーチンを考えました:実行中に方向選択が変更されると、プログラムは最初に両方のコマンド出力をゼロに駆動し、完全に停止するまでモーターを待ちます。その後、その任務を終了し、すべての制御をメインルーチンに戻します。

  if(Direction!=prevDirection){/ *モーターへの両方のPWM出力を強制終了* / analogWrite(_chMotorCmdCCW、0); analogWrite(_chMotorCmdCW、0); / *モーター速度が低下するまで待ちます* / do {RPM =60 *(float)readFrequency(_chSpeedRead、4)/ _ DiscSlots; } while(RPM> _minRPM); }  
<図>

速読

私のアプリケーションでは、安価なHC-020K速度センサーを使用しました。供給電圧のレベルでパルスを送信し、データシートには供給電圧が5Vであると記載されています。ただし、私のボードはArduino Dueであり、受け入れることができません。だから私はDueの3.3V出力から直接電力を供給しました、そしてはい、それは機能しました。そして、周波数とHC-020K出力を読み取るために以下の関数が書かれています。

  int readFrequency(int _DI_FrequencyCounter_Pin、float _ReadingSpeed){pinMode(_DI_FrequencyCounter_Pin、INPUT);バイト_DigitalRead、_DigitalRead_Previous =0; unsigned long _Time =0、_Time_Init; float _Frequency =0; if((_ ReadingSpeed <=0)||(_ReadingSpeed> 10))return(-1); else {_Time_Init =micros(); do {_DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if((_ DigitalRead_Previous ==1)&&(_ DigitalRead ==0))_ Frequency ++; _DigitalRead_Previous =_DigitalRead; _Time =micros(); } while(_Time <(_ Time_Init +(1000000 / _ReadingSpeed))); } return(_ReadingSpeed * _Frequency); }  

HC-020Kのホイールには20個のスロットがあることに注意してください。周波数として1秒あたりの回転数を取得するには、読み取り周波数を20で割るだけです。次に、結果に60を掛けて、RPMを取得する必要があります。

  RPM =60 *(float)readFrequency(_chSpeedRead、4)/ _DiscSlots;  

グラフィカルなタッチアップ

入力と結果を表示するために、Makeblock Me TFT LCDを使用し、CommandToTFT()関数を記述してコマンドを送信しました。実際、この機能の理由は、必要に応じて、プログラムの1行だけでシリアル接続ポイントを変更することです。

Cartesian_Setup()、Cartesian_ClearPlotAreas()、およびCartesian_Line()関数は、それぞれグラフィックプロット領域を準備し、水平軸の終わり(ここでは「時間」)に達したときにプロット領域をクリアし、グラフィックをプロットするように記述されています。ここでグラフィック機能に関心がある場合は、Makeblock Me TFT LCDのマニュアルを参照してください。これらは実際にはこのブログの範囲外であるため、ここでは説明しません。

終了

ここでは、グラフィック機能の有無にかかわらずプログラムを個別にコピーするので、速度と方向の制御の実装をスタンドアロンで、またはグラフィックを使用して確認できます。さらに、コードには、プログラムされた機能の詳細な説明があります。

最後に、通常、負荷がない場合でも、DCモーターの速度を定格速度の10〜20%未満に下げることはできません。ただし、始動後に無負荷のDCモーターにPID制御を使用することで、5%近くまで減らすことができます。

編集(2018年2月25日): PNPの代わりにNPNトランジスタを使用する場合は、両方のタイプの逆電流も考慮する必要があります。トリガーされる(スイッチがオンになる)と、電流はPNPトランジスタのエミッタからコレクタに流れますが、NPNタイプ(コレクタからエミッタ)では逆になります。したがって、PNPトランジスタはE(+)C(-)として極性化され、NPNの場合はC(+)E(-)である必要があります。

<図>

コード

  • グラフィカルなタッチアップを使用したコード
  • グラフィカルな修正なしのコード
グラフィカルなタッチアップを使用したコード Arduino
 / * ############################################### ## Makeblock Me TFTLCDの色定数########################################## ###### * /#define _BLACK 0#define _RED 1#define _GREEN 2#define _BLUE 3#define _YELLOW 4#define _CYAN 5#define _PINK 6#define _WHITE 7 / * ######## ####################################### I / O割り当て######## ######################################## * / int _chSpeedSet =A0、//速度setpoint _chKp =A1、// PIDコントローラーの比例係数読み取り値_chKi =A2、// PIDコントローラーの積分係数読み取り値_chKd =A3、// PIDコントローラーの微分係数読み取り値_chMotorCmdCCW =3、//カウンター用モーターへのPWM出力-時計回りに回す_chMotorCmdCW =2、//時計回りに回すためのモーターへのPWM出力_chSpeedRead =24、//速度の読み取り_chDirection =25; //方向セレクターの読み取り/ * ########################################### ####その他の定数############################################ ### * /#define _minRPM 0 //方向変更を開始するための最小RPM#define _maxRPM 6000 //最大RPM制限#define _Tmax 90 //グラフ化の最大時間制限#define_DiscSlots 20 //インデックスディスクのスロット数/ * ###############################################グローバル変数############################################### */弦Cartesian_SetupDetails; boolean Direction、prevDirection; //アラーム設定floatRALL =500.0、RAL =1000.0、RAH =4000.0、RAHH =4500.0; float Seconds =0.0、prevSeconds =0.0、prevRPM =0.0、prevRPMset =0.0、RPM =0.0、RPMset =0.0、OutputRPM =0.0、Kp =0.0、Ki =0.0、Kd =0.0、Kpmax =2.0、Kimax =1.0、Kdmax =1.0、E =0.0、Eprev =0.0、dT =1.0; / * ###### ######################################### CommandToTFT(TFTCmd)MakeblockMeのコマンド関数TFT LCD入力パラメータ:(Stri ng)TFTCmd:コマンド文字列############################################ ### * / void CommandToTFT(String TFTCmd){/ *表示に使用されるシリアル接続* / Serial1.println(TFTCmd); delay(5);} / * ########### CommandToTFT()の終了########### * // * ########### ################################## * // * ############ ################################### Cartesian_Setup(Xmin、Xmax、Ymin、Ymax、Window_X1、Window_Y1、Window_X2 、Window_Y2、MinDashQty、ColorF、ColorX、ColorY)MakeblockMeのデカルトXY軸描画関数TFTLCD入力パラメーター:(浮動小数点)Xmin、Xmax、Ymin、Ymax:軸範囲値(int)Window_X1、Window_Y1 ___:の左上隅グラフウィンドウ(int)Window_X2、Window_Y2 ___:グラフウィンドウの右下隅(int)MinDashQty _____________:最短軸のダッシュの数(int)ColorB、ColorX、ColorY:フレーム、X軸、およびY軸の用途の描画色外部関数CommandToTFT()。############################################ ### * / String Cartesian_Setup(float Xmin、float Xmax、float Ymin、float Ymax、int Window_X1、int Window_Y1、int Window_X2、int Window_Y2、int MinDashQty、int ColorF、int ColorX、int ColorY){/ *画面制限* / const int DisplayResolutionX =319、DisplayResolutionY =239; / *タイトル文字列を制限します* / String XminTxt; if(abs(Xmin)> =1000000000)XminTxt ="X =" +文字列(Xmin / 1000000000)+ "G"; else if(abs(Xmin)> =1000000)XminTxt ="X =" +文字列(Xmin / 100000)+ "M"; else if(abs(Xmin)> =1000)XminTxt ="X =" +文字列(Xmin / 1000)+ "K"; else XminTxt ="X =" +文字列(Xmin);文字列XmaxTxt; if(abs(Xmax)> =1000000000)XmaxTxt ="X =" +文字列(Xmax / 1000000000)+ "G"; else if(abs(Xmax)> =1000000)XmaxTxt ="X =" +文字列(Xmax / 100000)+ "M"; else if(abs(Xmax)> =1000)XmaxTxt ="X =" +文字列(Xmax / 1000)+ "K"; else XmaxTxt ="X =" +文字列(Xmax);文字列YminTxt; if(abs(Ymin)> =1000000000)YminTxt ="Y =" +文字列(Ymin / 1000000000)+ "G"; else if(abs(Ymin)> =1000000)YminTxt ="Y =" +文字列(Ymin / 100000)+ "M"; else if(abs(Ymin)> =1000)YminTxt ="Y =" +文字列(Ymin / 1000)+ "K"; else YminTxt ="Y =" +文字列(Ymin);文字列YmaxTxt; if(abs(Ymax)> =1000000000)YmaxTxt ="Y =" +文字列(Ymax / 1000000000)+ "G"; else if(abs(Ymax)> =1000000)YmaxTxt ="Y =" +文字列(Ymax / 100000)+ "M"; else if(abs(Ymax)> =1000)YmaxTxt ="Y =" +文字列(Ymax / 1000)+ "K";それ以外の場合、YmaxTxt ="Y =" +文字列(Ymax); / *制限* / int XminPx =Window_X1 + 1; int XmaxPx =Window_X2-1; int YmaxPx =Window_Y1 + 1; int YminPx =Window_Y2-1; / *オリジン* / int OriginX =XminPx +(int)((XmaxPx-XminPx)* abs(Xmin)/(abs(Xmax)+ abs(Xmin))); int OriginY =YmaxPx +(int)((YminPx-YmaxPx)* abs(Ymax)/(abs(Ymax)+ abs(Ymin))); / *フレーム* / CommandToTFT( "BOX(" + String(Window_X1)+ "、" + String(Window_Y1)+ "、" + String(Window_X2)+ "、" + String(Window_Y2)+ "、" + String( ColorF)+ ");"); / * X軸* / CommandToTFT( "PL(" + String(Window_X1 + 1)+ "、" + String(OriginY)+ "、" + String(Window_X2-1)+ "、" + String(OriginY)+ " 、 "+ String(ColorX)+"); "); / * Y軸* / CommandToTFT( "PL(" + String(OriginX)+ "、" + String(Window_Y1 + 1)+ "、" + String(OriginX)+ "、" + String(Window_Y2-1)+ " 、 "+ String(ColorY)+"); "); / *ダッシュ:ダッシュの最小数は「MinDashQty」で指定され、原点に対して最短の軸側でダッシュされます。他のセクションでは、マークするダッシュは、最短軸側に対する比率を考慮して決定する必要があります。 * / / *ダッシュ* / int XlengthLeft =abs(XminPx-OriginX); int XlengthRight =abs(XmaxPx-OriginX); int YlengthLower =abs(YminPx-OriginY); int YlengthUpper =abs(YmaxPx-OriginY); int XlengthLeft_Mod、XlengthRight_Mod、YlengthLower_Mod、YlengthUpper_Mod; if(XlengthLeft <=1)XlengthLeft_Mod =32767;それ以外の場合、XlengthLeft_Mod =XlengthLeft; if(XlengthRight <=1)XlengthRight_Mod =32767;それ以外の場合、XlengthRight_Mod =XlengthRight; if(YlengthLower <=1)YlengthLower_Mod =32767;それ以外の場合、YlengthLower_Mod =YlengthLower; if(YlengthUpper <=1)YlengthUpper_Mod =32767;それ以外の場合、YlengthUpper_Mod =YlengthUpper; int MinAxisLength =min(min(XlengthLeft_Mod、XlengthRight_Mod)、min(YlengthLower_Mod、YlengthUpper_Mod)); int XdashesLeft =MinDashQty * XlengthLeft / MinAxisLength; int XdashesRight =MinDashQty * XlengthRight / MinAxisLength; int YdashesLower =MinDashQty * YlengthLower / MinAxisLength; int YdashesUpper =MinDashQty * YlengthUpper / MinAxisLength; int DashingInterval =2; // Min.interval btw.dashes / * X-Dash L * / DashingInterval =(int)(XlengthLeft / XdashesLeft); if(!(DashingInterval <2))for(int i =OriginX; i> =XminPx; i- =DashingInterval)CommandToTFT( "PL(" + String(i)+ "、" + String(OriginY-2)+ " 、 "+ String(i)+"、 "+ String(OriginY + 2)+"、 "+ String(ColorX)+"); "); / * X-Dash R * / DashingInterval =(int)(XlengthRight / XdashesRight); if(!(DashingInterval <2))for(int i =OriginX; i <=XmaxPx; i + =DashingInterval)CommandToTFT( "PL(" + String(i)+ "、" + String(OriginY-2)+ "、 "+ String(i)+"、 "+ String(OriginY + 2)+"、 "+ String(ColorX)+"); "); / * Y-Dash-L * / DashingInterval =(int)(YlengthLower / YdashesLower); if(!(DashingInterval <2))for(int i =OriginY; i <=YminPx; i + =DashingInterval)CommandToTFT( "PL(" + String(OriginX-2)+ "、" + String(i)+ "、 "+ String(OriginX + 2)+"、 "+ String(i)+"、 "+ String(ColorY)+"); "); / * Y-Dash-U * / DashingInterval =(int)(YlengthUpper / YdashesUpper); if(!(DashingInterval <2))for(int i =OriginY; i> =YmaxPx; i- =DashingInterval)CommandToTFT( "PL(" + String(OriginX-2)+ "、" + String(i)+ " 、 "+ String(OriginX + 2)+"、 "+ String(i)+"、 "+ String(ColorY)+"); "); / *軸の端点値を表示するための座標の計算* / int XminTxtX =Window_X1-(int)(XminTxt.length()* 6)-1、XminTxtY =OriginY、XmaxTxtX =Window_X2 + 1、XmaxTxtY =OriginY、YminTxtX =OriginX、YminTxT =Window_Y2 + 1、YmaxTxtX =OriginX、YmaxTxtY =Window_Y1-12-1; / *コントロール:いずれかの座標が-1の場合、それは表示制限を超え、それぞれの値は表示されません* / if(XminTxtX <0)XminTxtX =-1; if((XminTxtY-12)<0)XminTxtY =-1; if((XmaxTxtX + 6 * XmaxTxt.length())> DisplayResolutionX)XmaxTxtX =-1; if((XmaxTxtY + 12)> DisplayResolutionY)XmaxTxtY =-1; if((YminTxtX + 6 * YminTxt.length())> DisplayResolutionX)YminTxtX =-1; if((YminTxtY + 12)> DisplayResolutionY)YminTxtY =-1; if((YmaxTxtX + 6 * YmaxTxt.length())> DisplayResolutionX)YmaxTxtX =-1; if(YmaxTxtY <0)YmaxTxtY =-1; / *範囲制限タイトル* / if((XminTxtX!=-1)&&(XminTxtY!=-1))CommandToTFT( "DS12(" + String(XminTxtX)+ "、" + String(XminTxtY)+ "、 '" + String(XminTxt)+ "'、" + String(ColorX)+ ");"); if((XmaxTxtX!=-1)&&(XmaxTxtY!=-1))CommandToTFT( "DS12(" + String(XmaxTxtX)+ "、" + String(XmaxTxtY)+ "、 '" + String(XmaxTxt)+ " '、 "+ String(ColorX)+"); "); if((YminTxtX!=-1)&&(YminTxtY!=-1))CommandToTFT( "DS12(" + String(YminTxtX)+ "、" + String(YminTxtY)+ "、 '" + String(YminTxt)+ " '、 "+ String(ColorY)+"); "); if((YmaxTxtX!=-1)&&(YmaxTxtY!=-1))CommandToTFT( "DS12(" + String(YmaxTxtX)+ "、" + String(YmaxTxtY)+ "、 '" + String(YmaxTxt)+ " '、 "+ String(ColorY)+"); "); / *戻り値文字列Cartesian_Setup()は、文字列パッキンググラフィック構成を次の形式で返します。 ""文字列は '<'で始まり、 '>'で終わります。 。各値は '、'で区切られます* / / *初期化* / String Cartesian_SetupDetails ="<"; Cartesian_SetupDetails + =(String(Xmin)+ "、"); Cartesian_SetupDetails + =(String(Xmax)+ "、"); Cartesian_SetupDetails + =(String(Ymin)+ "、"); Cartesian_SetupDetails + =(String(Ymax)+ "、"); Cartesian_SetupDetails + =(String(Window_X1)+ "、"); Cartesian_SetupDetails + =(String(Window_Y1)+ "、"); Cartesian_SetupDetails + =(String(Window_X2)+ "、"); Cartesian_SetupDetails + =(String(Window_Y2)+ "、"); / *クローズアウト* / Cartesian_SetupDetails + =">"; return Cartesian_SetupDetails;} / * ########### End of Cartesian_Setup()########### * // * ################################################ * // * ################################################# Cartesian_ClearPlotAreas(Descriptor、Color)MakeblockMeのプロット領域リセット/クリア関数TFTLCD入力パラメーター:(文字列)記述子:セットアップ記述子-Cartesian_Setup()によって返されます(int)Color ______:プロット領域を塗りつぶすために使用される色外部関数CommandToTFTを使用します()。################################################# * / void Cartesian_ClearPlotAreas(String Descriptor、int Color){int X1、Y1、X2、Y2; / *プロット領域の境界座標* / / *記述子から値を抽出* / / * L [0] L [1] L [2] L [3] W [0] W [1] W [2] W [3 ] * / / * Xmin Xmax Ymin Ymax Window_X1 Window_Y1 Window_X2 Window_Y2 * / float L [4]; int W [4]; / *記述子に格納されている値* / int j =0; / *カウンター* / String D_Str =""; for(int i =1; i <=(Descriptor.length()-1); i ++)if(Descriptor [i] =='、'){if(j <4)L [j] =D_Str.toFloat( );それ以外の場合、W [j-4] =D_Str.toInt(); D_Str =""; j ++; } else D_Str + =Descriptor [i]; / *オリジン* / int OriginX =(W [0] +1)+(int)(((W [2] -1)-(W [0] +1))* abs(L [0])/( abs(L [1])+ abs(L [0]))); int OriginY =(W [1] +1)+(int)(((W [3] -1)-(W [1] +1))* abs(L [3])/(abs(L [3 ])+ abs(L [2]))); / *プロットエリアのクリア* / //Area.1:X + Y + X1 =OriginX + 2; Y1 =W [1] + 1; X2 =W [2] -1; Y2 =OriginY-2; CommandToTFT( "BOXF(" + String(X1)+ "、" + String(Y1)+ "、" + String(X2)+ "、" + String(Y2)+ "、" + String(Color)+ "); "); //Area.2:X- Y + X1 =W [0] + 1; Y1 =W [1] + 1; X2 =OriginX-2; Y2 =OriginY-2; CommandToTFT( "BOXF(" + String(X1)+ "、" + String(Y1)+ "、" + String(X2)+ "、" + String(Y2)+ "、" + String(Color)+ "); "); //Area.3:X- Y- X1 =W [0] + 1; Y1 =OriginY + 2; X2 =OriginX-2; Y2 =W [3] -1; CommandToTFT( "BOXF(" + String(X1)+ "、" + String(Y1)+ "、" + String(X2)+ "、" + String(Y2)+ "、" + String(Color)+ "); "); //Area.4:X + Y- X1 =OriginX + 2; Y1 =OriginY + 2; X2 =W [2] -1; Y2 =W [3] -1; CommandToTFT( "BOXF(" + String(X1)+ "、" + String(Y1)+ "、" + String(X2)+ "、" + String(Y2)+ "、" + String(Color)+ "); ");} / * ########### Cartesian_ClearPlotAreas()の終わり########### * // * ############ ############################################# * // *# ############################################## Cartesian_Line(Xp、 Yp、X、Y、記述子、色)MakeblockMeのデカルト線関数TFTLCD入力パラメーター:(int)Xp、Yp _____:以前のプロット座標-y値vs x(int)X、Y _______:現在のプロット座標-y値vs x(文字列)記述子:セットアップ記述子-Cartesian_Setup()によって返されます(int)Color ______ :( x、y)で使用される色のマーキング外部関数CommandToTFT()。#############を使用します################################## * / void Cartesian_Line(float Xp、float Yp、float X、float Y 、String Descriptor、int Color){/ *記述子から値を抽出する* / / * L [0] L [1] L [2] L [3] W [0] W [1] W [2] W [3] * / / * Xmin Xmax Ymin Ymax Window_X1 Window_Y1 Window_X2 Window_Y2 * / float L [4]; int W [4]; / *記述子に格納されている値* / int j =0; / *カウンター* / String D_Str =""; for(int i =1; i <=(Descriptor.length()-1); i ++)if(Descriptor [i] =='、'){if(j <4)L [j] =D_Str.toFloat( );それ以外の場合、W [j-4] =D_Str.toInt(); D_Str =""; j ++; } else D_Str + =Descriptor [i]; / *オリジン* / int OriginX =(W [0] +1)+(int)(((W [2] -1)-(W [0] +1))* abs(L [0])/( abs(L [1])+ abs(L [0]))); int OriginY =(W [1] +1)+(int)(((W [3] -1)-(W [1] +1))* abs(L [3])/(abs(L [3 ])+ abs(L [2]))); int XminPx =W [0] + 1; int XmaxPx =W [2] -1; int YmaxPx =W [1] + 1; int YminPx =W [3] -1; if(Y> L [3])Y =L [3]; if(Y 
 =(OriginX-2))&&(DispXp <=(OriginX + 2)))||((DispYp>
 =(OriginY-2))&&(DispYp <=(OriginY + 2) ))||((DispX> =(OriginX-2))&&(DispX <=(OriginX + 2)))||((DispY> =(OriginY-2))&&(DispY <=(OriginY + 2) ))))CommandToTFT( "PL(" + String(DispXp)+ "、" + String(DispYp)+ "、" + String(DispX)+ "、" + String(DispY)+ "、" + String(Color )+ ");");} / * ########### Cartesian_Line()の終わり########### * // * ######## ####################################### * // * ####### ######################################## readFrequency(_DI_FrequencyCounter_Pin、_ReadingSpeed)周波数読み取り関数入力パラメータ:(int)_DI_FrequencyCounter_Pin:読み取るデジタルピン(float)_ReadingSpeed ____________:0 ... 10の間のカスタム読み取り速度(注.1)注.1:_ReadingSpeedは、変更をカウントする期間を指定する値です。 0(ゼロ)、負の値、または10より大きい値にすることはできません。_ReadingSpeedが変更された場合、1秒をこの値で割って、必要なカウント期間を計算します。例えば; --_ ReadingSpeed =0.1->入力は10秒間カウントされます(=1 / 0.1)--_ ReadingSpeed =0.5->入力は2秒間カウントされます(=1 / 0.5)--_ ReadingSpeed =2.0->入力は0.5の間カウントされます秒(=1/2)-_ ReadingSpeed =4.0->入力は0.25秒(=1/4)の間にカウントされます重要なことに、カウントエラーが増加するため、_ReadingSpeedの増加は特に低い周波数(通常は100 Hz未満)では不利であることに注意してください頻度を減らすことで最大20%〜40%######################################## ######## * / int readFrequency(int _DI_FrequencyCounter_Pin、float _ReadingSpeed){pinMode(_DI_FrequencyCounter_Pin、INPUT);バイト_DigitalRead、_DigitalRead_Previous =0; unsigned long _Time =0、_Time_Init; float _Frequency =0; if((_ ReadingSpeed <=0)||(_ReadingSpeed> 10))return(-1); else {_Time_Init =micros(); do {_DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if((_ DigitalRead_Previous ==1)&&(_ DigitalRead ==0))_ Frequency ++; _DigitalRead_Previous =_DigitalRead; _Time =micros(); } while(_Time <(_ Time_Init +(1000000 / _ReadingSpeed))); } return(_ReadingSpeed * _Frequency);} / * ########### end of readFrequency()########### * // * ######## ###################################### * // * ######## ####################################### controllerPID(RangeMin、RangeMax、_E、_Eprev、_dT 、_Kp、_Ki、_Kd)PIDコントローラー機能入力パラメーター:(フロート)RangeMin:出力の最小制限(float)RangeMax:出力の最大制限(float)_E _____:現在のエラー信号(float)_Eprev:前のエラー信号(float) _dT ____:秒単位の時間差(float)_Kp ____:比例係数(float)_Ki ____:積分係数(float)_Kp ____:微分係数調整手順:1。Kp=0、Ki =0、Kd =0に設定します。 2.システムが一定の周期(Pc)で振動するまで、Kpの増加を開始し、臨界ゲインKc =Kpに注意します。 3.最終係数を次のように調整します。 P制御のみの場合:Kp =0.50 * Kc PI制御のみの場合:Kp =0.45 * Kc、Ki =1.2 / Pc PID制御の場合:Kp =0.60 * Kc、Ki =2.0 / Pc、Kd =Pc / 8 4.微調整は、各係数をわずかに変更することで実行できます。##################################### ########## * / float controllerPID(float _E、float _Eprev、float _dT、float _Kp、float _Ki、float _Kd){float P、I、D; / *基本式:U =_Kp *(_ E + 0.5 *(1 / _Ki)*(_ E + _Eprev)* _ dT + _Kd *(_ E-_Eprev)/ _ dT); * / P =_Kp * _E; / *比例成分* / I =_Kp * 0.5 * _Ki *(_ E + _Eprev)* _dT; / *積分コンポーネント* / D =_Kp * _Kd *(_ E-_Eprev)/ _dT; / *派生コンポーネント* / return(P + I + D);} / * ###########コントローラーの終わりPID()########### * // *# ############################################# * // *# ############################################## 設定### ############################################ * / void setup() {Serial1.begin(9600); Serial1.println( "CLS(0);"); delay(20); analogReadResolution(12); pinMode(_chDirection、INPUT); //方向セレクターの読み取りpinMode(_chMotorCmdCCW、OUTPUT); //反時計回りに回転するためのモーターへのPWM出力pinMode(_chMotorCmdCW、OUTPUT); //時計回りに回転するモーターへのPWM出力//モーターへのPWM出力を最初に強制終了analogWrite(_chMotorCmdCCW、0); analogWrite(_chMotorCmdCW、0); //方向選択の初期読み取りDirection =digitalRead(_chDirection); // HIGH =CCW、LOW =CW prevDirection =Direction; //以下のセクションでTFTLCDを準備します// Cartesian_Setup(Xmin、Xmax、Ymin、Ymax、Window_X1、Window_Y1、Window_X2、Window_Y2、MinDashQty、ColorF、ColorX、ColorY)Cartesian_SetupDetails =Cartesian_Setup(0、_Tmax、_minRPM、_maxRPM、20 20、220、120、10、0、7、7); CommandToTFT( "DS12(250,10、 'Dir:CW'、" + String(_WHITE)+ ");"); CommandToTFT( "DS12(250,25、 '____ Set'、" + String(_YELLOW)+ ");"); CommandToTFT( "DS12(250,40、 '____ RPM'、" + String(_GREEN)+ ");"); / *アラーム値* / CommandToTFT( "DS12(250,55、 'AHH:" + String(RAHH)+ "'、" + String(_WHITE)+ ");"); CommandToTFT( "DS12(250,70、 'AH:" + String(RAH)+ "'、" + String(_WHITE)+ ");"); CommandToTFT( "DS12(250,85、 'AL:" + String(RAL)+ "'、" + String(_WHITE)+ ");"); CommandToTFT( "DS12(250,100、 'ALL:" + String(RALL)+ "'、" + String(_WHITE)+ ");"); / *アラームウィンドウ* / CommandToTFT( "BOX(240,55,319,115、" + String(_WHITE)+ ");"); / *アラームランプ* / CommandToTFT( "BOX(240,55,248,70、" + String(_WHITE)+ ");"); CommandToTFT( "BOX(240,70,248,85、" + String(_WHITE)+ ");"); CommandToTFT( "BOX(240,85,248,100、" + String(_WHITE)+ ");"); CommandToTFT( "BOX(240,100,248,115、" + String(_WHITE)+ ");");} / * ########################### ####################ループ############################# ################## * / void loop(){//初期化時間:PIDコントローラーに必要です。 int InitTime =micros(); //グラフのX軸自動リセットif(Seconds> 90.0){Seconds =0.0; Cartesian_ClearPlotAreas(Cartesian_SetupDetails、0); } //入力の読み取り/ *コントローラー係数* / Kp =Kpmax *(float)analogRead(_chKp)/ 4095; Ki =Kimax *(float)analogRead(_chKi)/ 4095; Kd =Kdmax *(float)analogRead(_chKd)/ 4095; / *方向セレクター* /方向=digitalRead(_chDirection); / * HIGH =CCW、LOW =CW * / / *実際のRPMおよびRPMセットポイント選択可能な最大RPMは5000であることに注意してください。* / RPM =60 *(float)readFrequency(_chSpeedRead、4)/ _DiscSlots; RPMset =5000 *(float)analogRead(_chSpeedSet)/ 4095; //計算とアクション/ *エラー信号、PIDコントローラー出力およびモーターへの最終出力(PWM)* / E =RPMset --RPM; float cPID =controllerPID(E、Eprev、dT、Kp、Ki、Kd); if(RPMset ==0)OutputRPM =0;それ以外の場合、OutputRPM =OutputRPM + cPID; if(OutputRPM <_minRPM)OutputRPM =_minRPM; if(OutputRPM> _maxRPM)OutputRPM =_maxRPM; / *反転時に方向を変更するこの関数ではグラフィック表示が実行されないことに注意してください。 * / if(Direction!=prevDirection){/ *モーターへの両方のPWM出力を強制終了* / analogWrite(_chMotorCmdCCW、0); analogWrite(_chMotorCmdCW、0); / *モーター速度が低下するまで待ちます* / do {RPM =60 *(float)readFrequency(_chSpeedRead、4)/ _DiscSlots; } while(RPM> _minRPM); } //出力の書き込みif(Direction ==HIGH)analogWrite(_chMotorCmdCCW、(int)(255 * OutputRPM / _maxRPM)); else analogWrite(_chMotorCmdCW、(int)(255 * OutputRPM / _maxRPM)); //グラフ化/ *方向を示す* / if(Direction ==HIGH)CommandToTFT( "DS12(280,10、 'CCW'、" + String(_WHITE)+ ");"); else CommandToTFT( "DS12(280,10、 'CW'、" + String(_WHITE)+ ");"); / *曲線のプロット* / Cartesian_Line(prevSeconds、prevRPMset、Seconds、RPMset、Cartesian_SetupDetails、_YELLOW); Cartesian_Line(prevSeconds、prevRPM、Seconds、RPM、Cartesian_SetupDetails、_GREEN); / * RPMセットポイント、PIDコントローラー係数、エラー信号、PIDコントローラー出力および最終RPM出力(PWM)の値を示します* / CommandToTFT( "DS12(20,150、 'Set:" + String(RPMset)+ "rpm" + "RPM : "+ String(RPM)+" rpm '、 "+ String(_WHITE)+"); "); CommandToTFT( "DS12(20,170、 'Kp =" + String(Kp)+ "" + "Ki =" + String(Ki)+ "" + "Kd =" + String(Kd)+ "" + "dT =" + String(dT * 1000)+ "ms '、" + String(_WHITE)+ ");"); CommandToTFT( "DS12(20,190、 'e =" + String(E)+ "" + "cPID =" + String(cPID)+ "" + "RPMout =" + String(OutputRPM)+ "'、" + String( _WHITE)+ ");"); / *アラームランプのリセット* / CommandToTFT( "BOXF(241,56,247,69、" + String(_BLACK)+ ");"); CommandToTFT( "BOXF(241,71,247,84、" + String(_BLACK)+ ");"); CommandToTFT( "BOXF(241,86,247,99、" + String(_BLACK)+ ");"); CommandToTFT( "BOXF(241,101,247,114、" + String(_BLACK)+ ");"); / *必要なアラームランプをアクティブにする* / if(RPM> =RAHH)CommandToTFT( "BOXF(241,56,247,69、" + String(_RED)+ ");"); if((RPM> =RAH)&&(RPM  RALL)&&(RPM <=RAL))CommandToTFT( "BOXF(241,86,247,99、" + String(_RED)+ ");"); if(RPM <=RALL)CommandToTFT( "BOXF(241,101,247,114、" + String(_RED)+ ");"); //前のサイクルで生成された値を保存しますEprev =E; prevRPMset =RPMset; prevRPM =RPM; prevSeconds =秒; prevDirection =方向; //制御アプリケーションのサイクルタイムと渡された秒数を計算しますdT =float(micros()-InitTime)/ 1000000.0;秒+ =dT; } 
グラフィカルなタッチアップなしのコード Arduino
 / * ############################################# ## I / O割り当て############################################ ### * / int _chSpeedSet =A0、//速度設定値_chKp =A1、// PIDコントローラーの比例係数読み取り値_chKi =A2、// PIDコントローラーの積分係数読み取り値_chKd =A3、// PIDコントローラーの微分係数読み取り値_chMotorCmdCCW =3、//反時計回りに回転するモーターへのPWM出力_chMotorCmdCW =2、//時計回りに回転するモーターへのPWM出力_chSpeedRead =24、//速度読み取り_chDirection =25; //方向セレクターの読み取り/ * ########################################### ####その他の定数############################################ ### * /#define _minRPM 0 //方向変更を開始するための最小RPM#define _maxRPM 6000 //最大RPM制限#define_DiscSlots 20 //インデックスディスクのスロット数/ * ########## #####################################グローバル変数############ #################################### * / boolean Direction、prevDirection; float RPM =0.0、RPMset =0.0、OutputRPM =0.0、Kp =0.0、Ki =0.0、Kd =0.0、Kpmax =2.0、Kimax =1.0、Kdmax =1.0、E =0.0、Eprev =0.0、dT =1.0; / * ###### ######################################### readFrequency(_DI_FrequencyCounter_Pin、_ReadingSpeed)周波数読み取り関数入力パラメータ:(int)_DI_FrequencyCounter_Pin:読み取るデジタルピン(float)_ReadingSpeed ____________:0 ... 10の間のカスタム読み取り速度(注.1)注.1:_ReadingSpeedは、変更をカウントする時間を指定する値です。 d。 0(ゼロ)、負の値、または10より大きい値にすることはできません。_ReadingSpeedが変更された場合、1秒をこの値で割って、必要なカウント期間を計算します。例えば; --_ ReadingSpeed =0.1->入力は10秒間カウントされます(=1 / 0.1)--_ ReadingSpeed =0.5->入力は2秒間カウントされます(=1 / 0.5)--_ ReadingSpeed =2.0->入力は0.5の間カウントされます秒(=1/2)-_ ReadingSpeed =4.0->入力は0.25秒(=1/4)の間にカウントされます重要なことに、カウントエラーが増加するため、_ReadingSpeedの増加は特に低い周波数(通常は100 Hz未満)では不利であることに注意してください頻度を減らすことで最大20%〜40%######################################## ######## * / int readFrequency(int _DI_FrequencyCounter_Pin、float _ReadingSpeed){pinMode(_DI_FrequencyCounter_Pin、INPUT);バイト_DigitalRead、_DigitalRead_Previous =0; unsigned long _Time =0、_Time_Init; float _Frequency =0; if((_ ReadingSpeed <=0)||(_ReadingSpeed> 10))return(-1); else {_Time_Init =micros(); do {_DigitalRead =digitalRead(_DI_FrequencyCounter_Pin); if((_ DigitalRead_Previous ==1)&&(_ DigitalRead ==0))_ Frequency ++; _DigitalRead_Previous =_DigitalRead; _Time =micros(); } while(_Time <(_ Time_Init +(1000000 / _ReadingSpeed))); } return(_ReadingSpeed * _Frequency);} / * ########### end of readFrequency()########### * // * ######## ###################################### * // * ######## ####################################### controllerPID(RangeMin、RangeMax、_E、_Eprev、_dT 、_Kp、_Ki、_Kd)PIDコントローラー機能入力パラメーター:(フロート)RangeMin:出力の最小制限(float)RangeMax:出力の最大制限(float)_E _____:現在のエラー信号(float)_Eprev:前のエラー信号(float) _dT ____:秒単位の時間差(float)_Kp ____:比例係数(float)_Ki ____:積分係数(float)_Kp ____:微分係数調整手順:1。Kp=0、Ki =0、Kd =0に設定します。 2.システムが一定の周期(Pc)で振動するまで、Kpの増加を開始し、臨界ゲインKc =Kpに注意します。 3.最終係数を次のように調整します。 P制御のみの場合:Kp =0.50 * Kc PI制御のみの場合:Kp =0.45 * Kc、Ki =1.2 / Pc PID制御の場合:Kp =0.60 * Kc、Ki =2.0 / Pc、Kd =Pc / 8 4.微調整は、各係数をわずかに変更することで実行できます。##################################### ########## * / float controllerPID(float _E、float _Eprev、float _dT、float _Kp、float _Ki、float _Kd){float P、I、D; / *基本式:U =_Kp *(_ E + 0.5 *(1 / _Ki)*(_ E + _Eprev)* _ dT + _Kd *(_ E-_Eprev)/ _ dT); * / P =_Kp * _E; / *比例成分* / I =_Kp * 0.5 * _Ki *(_ E + _Eprev)* _dT; / *積分コンポーネント* / D =_Kp * _Kd *(_ E-_Eprev)/ _dT; / *派生コンポーネント* / return(P + I + D);} / * ###########コントローラーの終わりPID()########### * // *# ############################################# * // *# ############################################## 設定### ############################################ * / void setup() {analogReadResolution(12); pinMode(_chDirection、INPUT); //方向セレクターの読み取りpinMode(_chMotorCmdCCW、OUTPUT); //反時計回りに回転するためのモーターへのPWM出力pinMode(_chMotorCmdCW、OUTPUT); //時計回りに回転するモーターへのPWM出力//モーターへのPWM出力を最初に強制終了analogWrite(_chMotorCmdCCW、0); analogWrite(_chMotorCmdCW、0); //方向選択の初期読み取りDirection =digitalRead(_chDirection); // HIGH =CCW、LOW =CW prevDirection =Direction;} / * #################################### #############ループ#################################### ########### * / void loop(){//初期化時間:PIDコントローラーに必要です。 int InitTime =micros(); //入力の読み取り/ *コントローラー係数* / Kp =Kpmax *(float)analogRead(_chKp)/ 4095; Ki =Kimax *(float)analogRead(_chKi)/ 4095; Kd =Kdmax *(float)analogRead(_chKd)/ 4095; / *方向セレクター* /方向=digitalRead(_chDirection); / * HIGH =CCW、LOW =CW * / / *実際のRPMおよびRPMセットポイント選択可能な最大RPMは5000であることに注意してください。* / RPM =60 *(float)readFrequency(_chSpeedRead、4)/ _DiscSlots; RPMset =5000 *(float)analogRead(_chSpeedSet)/ 4095; //計算とアクション/ *エラー信号、PIDコントローラー出力およびモーターへの最終出力(PWM)* / E =RPMset --RPM; float cPID =controllerPID(E、Eprev、dT、Kp、Ki、Kd); if(RPMset ==0)OutputRPM =0;それ以外の場合、OutputRPM =OutputRPM + cPID; if(OutputRPM <_minRPM)OutputRPM =_minRPM; if(OutputRPM> _maxRPM)OutputRPM =_maxRPM; / *反転時に方向を変更* / if(Direction!=prevDirection){/ *モーターへの両方のPWM出力を強制終了* / analogWrite(_chMotorCmdCCW、0); analogWrite(_chMotorCmdCW、0); / *モーター速度が低下するまで待ちます* / do {RPM =60 *(float)readFrequency(_chSpeedRead、4)/ _DiscSlots; } while(RPM> _minRPM); } //出力の書き込みif(Direction ==HIGH)analogWrite(_chMotorCmdCCW、(int)(255 * OutputRPM / _maxRPM)); else analogWrite(_chMotorCmdCW、(int)(255 * OutputRPM / _maxRPM)); //前のサイクルで生成された値を保存しますEprev =E; prevDirection =方向; //制御アプリケーションのサイクルタイムと渡された秒数を計算しますdT =float(micros()-InitTime)/ 1000000.0;} 

回路図

これは、PIDコントローラーを使用したDCモーターの速度制御と、逆転のために考慮すべきことを説明するためのプロトタイプです。

製造プロセス

  1. モーター用タングステン銅合金
  2. 実際のセンサーで効果を制御する
  3. Arduino Nano:ジョイスティックで2つのステッピングモーターを制御
  4. ArduinoUnoでLEDマトリックスを制御する
  5. 学校向けのSMART温度監視
  6. Arduino用の8ビットIOポートライブラリ
  7. Arduino用の64キープロトタイピングキーボードマトリックス
  8. ArduinoNano用TFTシールド-開始
  9. Arduino用の絶縁アナログ入力
  10. Arduino用の雷検知器
  11. 超クールな屋内ナビゲーション用ロボット