Arduinoでサーボ制御
夏休みに工業系先生向けの制御講習会を行うけど、 例年参加している人もでてくるので、少しは違う実験とすべく、 サーボモータの制御を入れてみようと計画中。 ということで、手持ちである共立電子の通信販売のサーボモータ(プチロボ)を動かしてみた。
#include <Servo.h> Servo motor ; // White D9 int d = 1 ; // Red 5V int v = 0 ; // Black GND void setup() { motor.attach( 9 ) ; } void loop() { // 0°〜180°〜0°を繰り返す。 motor.write( v ) ; v = v + d ; if ( v >= 180 ) { v = 179 ; d = -1 ; } else if ( v < 0 ) { v = 0 ; d = 1 ; } delay( 2 ) ; }
これで動かすと、このサーボモータでは、プログラム的には0-180で変化させているが、実際の変化幅は120°程であった。 delay(1)にすると、サーボの制御信号の変化にモータが追いつけないため、 90°ほどの変化幅となった。
ちなみに、最近購入したArduino Uno であったが、開発環境は Arduino 0022 に 更新しないと、Uno への書き込みができなかった。(0017はダメ)
加速度センサーと連動
加速度センサーで傾きを検出し、傾きに応じてサーボモータを動かしてみた。
#include <Servo.h> Servo motor ; int acc_x ; // Vcc--3.3V, GS1-GND g1.5 int acc_y ; // GND--GND , GS2-GND int acc_z ; // X----AN0 , SLP-3.3V int ang ; // Y----AN1 // Z----AN2 #define OFFSET 335 void setup() { motor.attach( 9 ) ; acc_x = acc_y = acc_z = 0 ; } void loop() { acc_x = (3*acc_x + analogRead(0) - OFFSET )/4 ; // 加重移動平均 acc_y = (3*acc_y + analogRead(1) - OFFSET )/4 ; acc_z = (3*acc_z + analogRead(2) - OFFSET )/4 ; ang = (int)( atan2( acc_y , acc_x ) / 3.141592 * 180 ) ; ang = constrain( ang , 0 , 180 ) ; delay( 2 ) ; }
リストを用いたスタックとキュー
リスト操作の演習中だけど、残り授業もあと(今日を入れて)あと2回ということで、 リスト関連のネタが未消化にならないように、スタックとキューについて説明。
スタック
配列を用いた、LIFO(Last In First Out)=スタックであれば、一般的に 以下のようなコードになる。
int stack[ 100 ] ; int sp = 0 ; void push( int x ) { stack[ sp++ ] = x ; } int pop() { return stack[ --sp ] ; }
しかし、この方法では、配列サイズ以上のデータは保存できない。 これをリストを使うことでサイズを気にしないスタックを実現できる。
struct List* stack = NULL ; void push( int x ) { stack = cons( x , stack ) ; } int pop() { int ans = stack->data ; struct List* del = stack ; stack = stack->next ; free( del ) ; return ans ; }
キュー
待ち行列(Queue)は、FIFO(First In First Out)を配列で実装する場合、 一般的には、以下のようになる。ただしエラー対策は記載していないので、要注意。
int que[ 100 ] ; int wp = 0 ; // 書き込み用ポインタ int rp = 0 ; // 読み出し用ポインタ void put( int x ) { que[ wp ] = x ; wp = (wp + 1) % 100 ; // 循環させる } int get() { int ans = que[ rp ] ; rp = (rp + 1) % 100 ; // 循環させる return ans ; }
このような配列の領域を使い切ったら、先頭から再利用するような方法は、 リングバッファなどと呼ばれる。 このような待ち行列は、キー入力バッファや、プロセス待ち行列などに よく利用される。しかし、このプログラムでも、配列サイズ以上の データは保存できないので、リストを用いる。
struct List* top = NULL ; struct List** tail = &top ; void put( int x ) { *tail = cons( x , NULL ) ; tail = &( (*tail)->next ) ; } int get() { int ans = top->data ; struct List* del = top ; top = top->next ; free( del ) ; return ans ; }
ただし、このプログラムは、常に1件以上データがリストに入っている場合は 問題がないが、get() を実行して、データ件数が0件になると、tail の指す先が おかしくなるので注意が必要。
また、待ち行列では、先頭ポインタと末尾ポインタの2つが必要であるが、 リスト構造の末尾のNULLを、先頭データを指すようにする循環リストと する場合も多い。特に、プロセス待ち行列を実装するときのラウンドロビン方式 などでは、末尾まで処理が及んだ次は先頭に戻って処理を行うため、 循環リストは都合がいい。
サーバ室の温度じゃないな
朝起きたら、監視システムから大量のメール。 高専のWebサーバがAM3:00頃から止まっているみたい。 後で聞いた話だと、ファイアウォールのトラブルで、それなりに復帰。
その復帰の話を聞いた後、しばらくして お守りをしている緊急連絡システムが繋がらないとの警告。 5年もののサーバなので、いつお亡くなりになってもおかしくはない。 (ホットスタンバイ機があるのでそんなに心配するほどじゃないんだけどね。) んで、実際にサーバ室に向かうと、マシンは正常に動いている。 どうも間に入れているファイアウォール的なルータが気絶していたみたい。 電源リセットで普通に復帰してくれた。
んで、下の写真がサーバ室の温度計。クーラー効いているんだけど、 何台ものマシンがあるから、29℃….普通のサーバ室の温度じゃない。 普通は24℃設定で、最近の省エネのニュースで26℃でも問題ないとか 話題があがっていたからな…
ソフトウェア開発工程とアジャイル
UMLの説明により、オブジェクト指向プログラミングでの設計の方法を解説し、 UML作成の課題にも取り組んでもらっているので、 残りでソフトウェア開発工程の説明を行う。
ウォーターフォールモデル
ウォーターフォールモデルは、トップダウン型の ソフトウェア開発において古くから用いられている手法で、 「要求定義」「外部設計(概要設計)」「内部設計(詳細設計)」「開発(プログラミング)」 「テスト」「運用」などの作業工程に分割する。 原則として前工程が完了しないと、次の工程に進めず、設計中にプログラミングを始めるなどの並行作業は行わない。
原則として並行作業を行わないので、ガントチャートなどでプロジェクト管理などを 行うことが多い。
スパイラルモデル
ウォーターフォールモデルは、前工程に戻らないことから、 細かな仕様変更に対して対応が困難となる。 このため、内部の詳細設計をある程度決めて全体を開発するといった、 ボトムアップ方式もある。 しかしながら、全体像の設計の詰めの甘さから、外部設計段階で内部設計の見直しが 発生することも多く、単純なボトムアップ方式は困難である。
このトップダウンとボトムアップの両方の良さをとる方式として、 スパイラルモデル がある。この方式では、設計とプロトタイピングを繰り返し行いながら 開発をしていく反復型開発方式である。
スパイラルモデルは、組織の改善でのPDCAサイクルと同じものと考えられる。 この方式は、プログラム規模やスケジュール予測もある程度でき、 要求仕様の変更があっても対応がしやすいことから、 大規模プロジェクトでもよく利用されている。
アジャイルソフトウェア開発
アジャイル(agile:機敏な)とは、敏速かつ適応的にソフトウェア開発を行う、 軽量な開発手法である。スパイラルモデルのPDCAサイクルよりも小さな、 反復(イテレーション)とよばれる短い開発単位(1W〜4W)を採用し、 1反復で1機能を開発する。 この反復では、他の古くからの開発手法と同じく、 計画、要求分析、設計、実装(コーディング)、テスト、文書化を行う。
アジャイルでは、動くものがあるという点で、利用者に使ってもらって 反応・感想を踏まえて改善ができる。 この方法をさらに推し進めた物に、 エクストリーム・プログラミング(XP)がある。 XPでは、自動テストなどを取り入れ、短期のイテレーションを推し進める。 一般的には完成に近づくにつれ、仕様変更は困難となるが、 オブジェクト指向プログラミングを取り入れ、 クラスの細部を変更しても全体に影響が及ばないような設計となっていることが 重要となる。
XPでは、コミュニケーション・シンプル・フィードバック・勇気・尊重といった5つの 価値で開発をとらえる。 開発にあたり、次のようなプラクティス(習慣)を与える。
- テスト駆動開発(実装の前にテスト方式を明確化)
- ペアプログラミング(2人1組でコーディング役とチェック&ナビゲート役を交代しながら行う)
- リファクタリング(完成済みのコードでも随時改善を行う。外部動作が変わらないように内部構造をより良いものにする)
- ソースコードの共同所有(バージョン管理システムの併用が重要:CVS,Subversion,RCS,Gitが有名)
リスト構造の演習
先週にリスト構造への先頭挿入型の追加と、末尾追加型のリスト処理を説明したので、 今週は演習。 テーマは以下のデータ構造にてデータを入力し、何らかの簡単なデータ処理(特定データの検索など)。
- 名前と電話番号のデータ
- 名前と誕生日のデータ
- 名前と年齢のデータ
コマンドプロンプトで絶対PATH,相対PATHの演習
コマンドプロンプトで相対PATH,絶対PATHの説明をした後、 以下の演習の内容をレポートとして提出してもらう。
ミニ課題
以下に示すファイルやディレクトリの生成を、コマンドプロンプトを用いた作業で行い、その際にタイプした命令と出力された内容を記載せよ。 ただし各作業では、絶対PATHを使用するとか相対PATHを使用するといった条件を満たすこと。
- Z:ドライブ上に、以下のような構造を作成せよ。 作成の際には、絶対PATHのみを使用すること。
- 上記のファイルbirthday.txt,like.txt の内容を表示せよ。 ディレクトリの移動には絶対PATHを使用、ファイルの表示には相対PATH。
- "cd Z:\20110705\saitoh"でディレクトリを移動した後、自分のbirthday.txt,like.txt を表示する命令を、相対PATHを使用して。 ただしディレクトリの移動はしないこと。
- 作成したディレクトリの内容を、すべて削除せよ。相対PATHや絶対PATHの方法は問わない。
Z:¥--+--[20110705]--+--[自分の名前]--+--(birthday.txt) 内容は誕生日 | +--(like.txt) 内容は好きな物 | +--[saitoh]----+--(birthday.txt) 1965/02/07 +--(like.txt) cats...
C言語でファイル操作
C言語での簡単なファイル操作ということで、下記のようなプログラムを示す。 特に「Windowsのディレクトリ名区切り文字がC言語のエスケープ文字」にあたるため、 問題が発生することを説明する。
#include <stdio.h> void main() { FILE* fp ; fp = fopen( "Z:¥¥abc.txt" , "r" ) ; if ( fp != NULL ) { char name[ 20 ] ; int age ; while( fscanf( fp , "%s%d" , name , &age ) == 2 ) { printf( "%s %d¥n" , name , age ) ; } fclose( fp ) ; } }
当面はOSと絡んだC言語での注意点を説明するのが目標。 来週は、fopen( … , "r" ) での「テキストモード/バイナリモード」の説明を行う予定。
絶対/相対PATHの演習とファイル処理
先週の講義でPATHの概念を説明したので、 授業前半でコマンドラインを用いてPATHの演習を行い、 後半はC言語でのファイル処理ということでfopen,fclose等の説明。
絶対/相対PATHの演習
簡単な演習ということで、Windowsのコマンドライン(cmd.exe)を起動し、 dir , type , cd , mkdir , rmdir , echo string>PATH などのファイル操作を 説明する。
最初は、C:¥Windows¥Help ディレクトリなどを使って ディレクトリの中を色々と移動しながら、絶対PATH/相対PATHを理解してもらう。 後半は、カレントドライブ(Windows固有の概念)を Z: に移動し、 Z:¥Japan¥Fukui¥Echizen¥yourname.txt といったような ディレクトリやファイルを作ったり消したりしながら、コマンドラインの操作を 理解してもらう。 途中、エクスプローラでGUIも横で見ながら、作業をしてもらう。
fopen() , fclose() などの説明
授業の後半は、fopen() , fclose() の説明として、 以下の様なサンプルプログラムを動かして fopen / fclose を説明。 来週は、説明が不十分だった、バイナリモード/テキストモード、 fgets , fputs , fgetc , fputc といった行入力,文字入力を説明し、 その翌週あたりに実際の演習とする予定。
(( fopen / fclose サンプル )) FILE * fp ; if ( (fp = fopen( "hoge.txt" , "rt" )) != NULL ) { int c ; // 入力データが無くなるまで、ファイルから1文字入力し、それを出力。 while( (c = fgetc( fp )) != EOF ) { printf( "%c" , c ) ; } fclose( fp ) ; }
また、fopenでの PATHの指定では、Windowsの場合ディレクトリ区切り文字が "¥"であるため、C言語の文字定数として、"C:¥test.txt" といった書き方を すると、"¥t"の部分がタブ文字として扱われて、ファイルが開けなくなる 可能性があることを説明する。 これらの対応として、WindowsのC言語ではディレクトリ区切り文字に、 "/"も使えることなども補足説明を行う。
FILE* fp ; if ( (fp = fopen( "c:¥¥test.txt" , "rt" )) != NULL ) { char name[ 100 ] ; int point ; int sum = 0 ; while( fscanf( fp , "%s %d" , name , &point ) == 2 ) { printf( "%s さんの点数は %d¥n" , name , point ) ; sum += point ; } printf( "合計は %d¥n" , sum ) ; fclose( fp ) ; }
evernoteの表の修正
最近、クラウド利用として、DropboxやらEvernoteやらを便利に使っている。 Evernoteは最初、そんなのDropboxやGoogle Docでいいじゃん…って思ってたけど、 使ってみるとエディタと統合されていて、それなりに便利。
ただ、表の編集機能はかなりダメダメ。難しい機能つけるとiPhoneアプリやAndroidアプリが無駄に複雑化しそうだし、妥協点とは思うけど。 丁度、今扱っているデータで、表を使っているんだけど、他の人にデータを見せるときに プロジェクタ投影したら画面幅が狭い状態になり、その時に編集作業を行ってから、表幅が右に余白があるのに狭く表示されるようになってしまった。 しかし、変更しようにも編集機能が無い。
Evernoteはデータは、XML形式のデータに過ぎないらしいので、 Emacs の evernote-mode を使ってみた。これを使うと、Emacs のエディタで、 テキストを編集できる。 んで、今回は、Emacsでノートの表の<table …. width="70%"> の部分を見つけて、 手作業でwidth="100%" への変更を行った。 やっぱり、面倒なんだけど…
リストの生成
リスト構造の生成。テスト直しの後に実施した説明が抜けているので、 リストの生成と処理の部分も含めて2週分を記載。
リスト生成の基礎
リスト構造のイメージを分かってもらうために、面倒だけど データ生成を単調に書いた例。
struct List { int data ; struct List* next ; } ; // 最初は単純に... // malloc失敗チェックは省略 struct List* top ; top = (struct List*)malloc( sizeof( struct List ) ) ; top->data = 1 ; top->next = (struct List*)malloc( sizeof( struct List ) ) ; top->next->data = 2 ; top->next->next = NULL ;
さすがに面倒なので、補助関数を使う。
// 補助関数を使って struct List* cons( int x , struct List* n ) { struct List* ans = (struct List*)malloc( sizeof( struct List ) ) ; if ( ans != NULL ) { ans->data = x ; ans->next = n ; } return ans ; } struct List* top = cons( 1 , cons( 2 , NULL ) ) ;
リストを使った簡単な処理
リストの末端には、これ以上データが続いていないことを表すNULLが入っている。
void print( struct List* p ) { for( ; p != NULL ; p = p->next ) printf( "%d¥n" , p->data ) ; } int sum( struct List* p ) { int s = 0 ; for( ; p != NULL ; p = p->next ) s += p->data ; return s ; } void main() { struct List* top = cons( 1, cons( 2, cons( 3, NULL ) ) ) ; print( top ) ; printf( "%d¥n" , sum( top ) ) ; }
リスト構造の応用では、2分木なども説明するので、その説明の導入として、 再帰で同じ処理を書いてみる。
void print( struct List* p ) { if ( p != NULL ) { printf( "%d¥n" , p->data ) ; print( p->next ) ; } } int sum( struct List* p ) { if ( p == NULL ) { return 0 ; } else { return p->data + sum( p->next ) ; } }
入力しながらリストを追加
手作業でリストを生成するような処理では、汎用性が無いので、 データを入力しながらリストを生成してみる。
void main() { struct List* top = NULL ; int x ; while( scanf( "%s" , &x ) == 1 ) { top = cons( x , top ) ; } }
しかし、上記の例では、新しい要素をリストの先頭に挿入する方法なので、 データは入力順序と逆順に保存される。 入力順序と同じように、最後のデータが末尾に入るように書くには、 末尾に挿入するデータの場所を覚えるポインタ(tail)を使って、 以下のように書くことができる。
void main() { struct List* top = NULL ; struct List** tail = &top ; int x ; while( scanf( "%s" , &x ) == 1 ) { *tail = cons( x , NULL ) ; tail = &( (*tail)->next ) ; } }