12回目(2020年度秋) 応用プログラミング演習(制御工学)(2)
P制御では,偏差 e ( = 入力SV - 応答PV ) に比例ゲイン
Kp をかけて操作量 MV を生成するが,偏差がゼロにならない(=定常偏差またはオフセットと言う)問題があったことを思い出して下さい.
実際の制御機器では,比例(P)に加え,偏差の積分(Integral)や微分量(Differential)を利用するPID制御が広く用いられています.
P制御では,操作量 MV は
MV = Kp e
であったが,PID制御ではこれに制御の要素を加えて,
MV = Kp (e + (1 / Ti) ∫e dt + Td (d e / d t))
の項を追加して,操作量MVを計算します.ここで,Kp
は比例ゲイン,Ti は積分時間(積分ゲイン),Td
は微分時間(微分ゲイン)と呼ばれ,PID制御では,偏差の積分値や微分値などを用いて,滑らか,かつ,速やかに目標値に向かって制御することができるのです.
また,P制御の問題点であった定常偏差も大幅に減らせ,概ねPID制御では目標値に正しく到達する.
ただ,目標値への到達に時間がかかるため,刻々と目標値が変化する場合はD制御を加えて(=PID),素早く目標値に到達させる方法を選択することもある.
ライントレースロボットのように目標値が変化する場合はIを用いないPD制御が用いられることがあることも知っておきましょう.
グラフィックの利用
前回の課題は,コンソールに文字・数値を出力し,Excelを用いてグラフ化する方法を使い,前回の課題の参考資料では,コンソールに文字でグラフを示すという,古式ゆかしい方法を使ってみました.
コマンドラインでの結果の表示は,シミュレーション結果を簡単にチェックすることができ便利であるが,値の変化や時間変化などは数値の羅列よりもグラフで確認したいものです.
ここではExcelに頼らず,C言語のプログラムだけを用いて図を描く方法を示していきます.
本格的な3次元コンピュータグラフィクス(CG)の実現には多くのコードの記述が必要となるが,ここでは単純な2次元グラフィックの利用を扱います.
以下を参考に,グラフィックを利用することが可能です.
参考ページ windows グラフィック 入門
なお,必ずmogra_2.fも併せてダウンロードしておいて下さい.
これはC言語のヘッダファイルなので,テキストエディタで開くことができる.
このファイルを使用するには,ソースファイルの先頭で
#include "mogra_2.h"
と記述することで,グラフィクス関数が利用可能となる.
注意事項
プログラムを停止する場合,コマンドラインのコンソールをクリックして有効化した後,[Ctrll]+[C]を押して終了して下さい.
参考:include文の < > と " "について
上の例のように#include "..."とすることで,
ソースファイルと同じフォルダ内の .h ファイルをincludeすることができる.
したがって,自作などのヘッダファイルのincludeには,"
"を使用する.
#include <...> では,ライブラリ関数などあらかじめ設定した検索場所(ヘッダーファイルの検索パス)に置かれているヘッダファイルしか読み込まない.
main関数も,関数
これまで関数には引数があり,戻り値があり,それぞれ型があってやり取りをするという話は
何度か聞いたと思います.ではmain関数はどうなるのでしょうか?
関数であるから,引数も戻り値もあります.では,どこで引数を獲得するのでしょうか?
例えばコマンドライン上で以下のようにタイプしたとします.
c:\>sample.exe 1.52 6.44 9.81 |
main関数に引数を設定すれば,1.52,6.44,9.81をそれぞれ値として取得,プログラム内で利用
することができます.
ソフトウェアを使う,と考えた場合,実行ファイルが起動されているのですが,実行される部分は
main関数ですので,上記のようにデータ(引数)をやり取りする手段が用意されているのです.
方法は,以下の記述法となります.
int main(int argc,char *argv[])
これまでのおなじみの書き方とは違っていることが分かるかと思います.
コラム: main関数の戻り値について(終了コード)
さて,main関数の表記について,いろいろなものを見たことがあると思います.例えば,
main()
void main()
void main(void)
int main()
int main(void)
などでしょうか.厳密に言えば,規格であるANSIに基いた記述をすべきであると言い放って
しまえばそれまでですが,実際にはどれでもコンパイルされてしまいます.(アラートが出る
こともあります.)
ここでソフトウェアがどのように実行されるかを説明します.
ソフトウェア実行ファイルとランタイムライブラリの関係
作成したソフトウェアが直接PCを動かすわけではなく,ソフトウェアの実行により生成された
コマンドがすでに用意されているランタイムライブラリやOSを介して動作するような構造と
なっています.その際に,細かい命令の単位できちんと実行されたか確認するような動作が
含まれているのです.
プログラムの根幹となるmain関数も実行が終わったら,終了したこと自体,ならびに正常に
実行が完了したことをシステムであるOSに伝える必要があるのです.
従って,画面などに表示されることはありませんが,戻り値を返すことが求められるのです.
そのために終了コードと呼ばれるものがあり,正常であれば,0が渡されます.
何も処理しないmain関数であったとしても,以下のように記す必要があります.
C言語 | int main() { return 0; } |
C++言語 | int main(void) { return 0; } |
実はCとC++どちらの表記でも正常にコンパイルされますが,C++でint
main()とした場合は,
暗黙の型としてvoid型として扱われるためなのです.但し,もう少し厳密に説明すると,
int main() | int main(void) | |
C言語 | 引数をチェックしない | 引数がない |
C++言語 | 引数がない | 引数がない |
という意味であるため,int main(int argc,char
*argv[])のように,コマンドラインにて引数を
与えるつもりがない場合,int main(void)と書いた方が分かりやすいと言えますが,int
main()
でも文法(規格)上は何の問題もありません.
従って,厳密には, void main()やvoid main(void)では終了コードが返せないため,プログラム
終了したのか,それがOSには分からなくなってしまいます.これを厳密に回避するには,
C言語 | void main() { exit(0); } |
C++言語 | void main(void) { exit(0); } |
のように記述すれば,main関数の末尾で[強制終了]の上,正常終了である[終了コード0]を
システムに伝えるという処理を行うことが出来ますが,強制終了という意味では穏やかでは
ないのです.
main()の場合,厳密には文法ミスですが,コンパイラの方で足りない分を適当に補って何事も
ないコードにしておいてくれると考えた方が良さそうです.
結論としては,以下のいずれかで記述した方がよいと言えます.
コマンドラインでmain関数に引数を与えない | int main()またはint main(void) |
コマンドラインでmain関数に引数を与える | int main(int argc,char *argv[]) |
話を戻しましょう.コマンドラインで,引数を受け取るには,以下のように記述します,
int main(int argc,char *argv[])
引数名がargcやargvである必要はありませんが,殆どの解説書でこれらの名前が充てられて
いるので,慣例に従って説明します.
先ほどの例のようにタイプしたとします.
c:\>sample.exe 1.52 6.44 9.81 |
実行ファイル名の後,1.52 6.44 9.81という3つの変数が記述されています.上記のようにmain
関数に記述しておけば,以下のように認識されます.
argc | 引数の数 → 配列数 | 3 |
argv[] | 引数の配列 → 各引数の中身 但し,文字列として認識される |
1.52 6.44 9.81 |
サンプルプログラムを見て下さい.
#include <stdio.h> int main(int argc,char *argv[]){ int i; for(i=0;i<argc;i++){ printf("argv[%d] : %s\n",i, argv[i]); } return 0; } |
実行結果
argv[0] : c:\sample.exe argv[1] : 1.52 argv[2] : 6.44 argv[3] : 9.8 |
0番目の文字列で実行ファイル(フルパス) 1番目の数値の文字列 2番目の数値の文字列 3番目の数値の文字列 |
上記の通りですが,ちょっと扱いにくい点がありますので考えてみましょう.
コマンドラインにタイプされたものはすべて文字列として扱われ,スペースを区切りとして,それ
ぞれ長さの異なる複数の文字列として扱われます.しかも,それぞれの文字列は配列のポインタ
として扱われるため,アクセスの仕方を間違えると,本当に必要な引数が獲得できない可能性が
あるのです.
そこで,以下のように用意されている関数を用いて文字列配列の各要素を浮動小数点に変換
します.
文字列はそれ自体が配列であるため,(double)といったキャストを行うことができないのです.
#include <stdio.h> #include <stdlib.h> int main(int argc,char *argv[]){ int i; char *ep=NULL; for(i=1;i<argc;i++){ printf("argv[%d] : %lf\n",i, strtod(argv[i],ep)); } return 0; } |
strtod関数を利用するために必要なヘッダファイル strtod関数を利用するために必要な変数ポインタ 文字列を数値に変換する関数 |
なお,文字列配列の0番目は,実行ファイルのフルパスが格納されますので,無視して数値の配列に
格納された値は以下のものとなります.
argv[1] : 1.52 argv[2] : 6.44 argv[3] : 9.81 |
従って,これまでプログラム本文中で記述したり,scanfで取得していた数値は,プログラム実行時にまとめて指定することが可能となります.
コマンドライン引数を利用するメリット
ソースファイル内の数値を変更してコンパイルすれば,もちろん必要な演算を行うことはできるのは確か
です.しかし,コンパイルしなおすのは時間が掛かり過ぎであるだけでなく,ソースファイルの他の部分を
うっかり変更してしまう危険性があるので,一度生成させた実行ファイルを変えることなく,必要な変数の
値のみを与える方法は有効なのです.
注意すべき点
実行ファイル名 変数 変数・・・のように記述するため,必要な値がプログラム内の必要な変数に格納
されたかどうか分からない危険性があります.従って,獲得されたコマンドライン引数を表示するような
工夫が必要となります.
※ 慣例的ですが,実行ファイル名 /?や実行ファイル名
/hと入力すれば,何番目にどのような値を入力
する必要があるか説明が表示できるようにプログラムを作成する方法もあります.
(参考) コマンドライン引数を用いない方法
コマンドライン引数が便利な場合もあれば,毎度引数を入力する必要があるため煩わしい場合もあります.
コンパイルを繰り返さず,複数の引数を扱うために,別途決められた名前のテキストファイルを作成しておき
これを読み込ませる方法もあります.例えば,sim.txtとかsim.iniのような名前をつけたりすればよいのです.
※ コマンドライン引数にファイル名を指定するという方法もあります.
複数の変数をプログラムで扱う工夫についてまとめ
方法 | メリット | デメリット |
対話的に変数を入力 | 1変数ごとに確認しながら 入力できる |
変数の数が多い場合面倒であり, 入力ミスを招く可能性がある. |
コマンドライン引数 | 一括して変数を入力でき, 軽快な操作となる. |
条件を変えたときは,全変数を 入力しなおさなければならない. |
テキストファイルの読み込み | 計算条件を予め検討して 用意しておくことができる. |
テキストエディタで編集の必要があり, スペース,タブ,改行コードでエラーを起こす. |
課題1 比例制御およびPID制御についてそれぞれ以下のファイルをダウンロードし,シミュレーションを行いましょう.速度型・位置型のどちらを用いても構いません.ただし,Macを利用している学生は,課題1 Plusをやって下さい.Windosユーザも以下の内容を試した上で課題1 Plusに挑戦してみて下さい.
速度型アルゴリズム p_p_gra.c
位置型アルゴリズム p_v_gra.c, p_v_gra.cpp
速度型アルゴリズム pid_p_gra.c
位置型アルゴリズム pid_v_gra.c, pid_v_gra.cpp
前回同様,時定数T=2.0秒,シミュレーション時間SimTは5〜10.0秒にし,目標値はSV=5000[rpm],Kpを自由に調整して良いと思われる応答になるよう調整し.今回のソースではグラフが表示されるはずなので何回か試行しながら値を決めましょう.
※ プログラムを停止するには [Ctrl] + [C]
という操作をして下さい.
※ ソースファイルの拡張子はcppではなくcにしておいて下さい.
参考ページ windows グラフィック 入門
なお,必ずmogra_2.hも併せてダウンロードしておいて下さい.
提出するもの: 自ら書き換えたソースファイル,グラフの入ったファイル※2
※2 グラフの提出の方法
画面上のウィンドウに出力されたグラフを簡単に出力するには,[Print
Screen]キーを押し,パワーポイントやペイントなどに貼り付け,それを保存すればよいのです.ppt形式でもjpg,
png, gifのどれでも構いません.
課題1 Plus
Macを利用している学生はこちらをやって下さい.Windosユーザも課題1の内容を試した上で挑戦してみて下さい.
まず,GNUPlotをC言語に組み込む方法を読んで下さい下さい.
その上で,課題1のソースファイルをGNUPlotで書き出すように書き換えて下さい.
課題1の内容について取り組み,グラフをGNUPlotで作成して下さい.出力の方法,提出するものは同様です.
課題2 上記プログラムのどれかひとつを選択し,コマンドライン上で引数を受け取ることができるように変更したものを作成して下さい.
提出するもの: 自ら書き換えたソースファイル
課題3
数値シミュレーションを行うにあたって,即時グラフを表示することがどのように有効であるか,100字程度で論ぜよ.
提出物: 本課題について論じたWordファイル
(.docx形式)
課題3
前回は,シミュレーションした数値をグラフ化して結果を比較し,与えたいパラメータを検討し,今回は,簡易的にグラフをその場で表示して,応答を見ながら与えたいパラメータを検討しました.
数値シミュレーションを行うにあたって,即時グラフを表示することがどのように有効であるか論じて下さい.
また,前回,今回のように与えたいパラメータを決めるにあたって,さらに検討すべきことはどのようなことがあるか(何が不足しているか)について説明して下さい.
ヒント: 理論の扱い
提出するもの: 本課題について論じたWordファイル