マイナビ出版刊 吉野耕司 著 「60日でできる!二足歩行ロボット自作入門」で例として製作した
ロボット「Pen4」について、書籍の中での完成状態を起点とした追加の製作や試行錯誤の記録を
記した製作日誌です。
このページは「マイコンボードキットの代替2」としてまとめるつもりの記事の予備調査 兼 下書き
として書き始めたものなので、分かりやすく古い記事から新しい記事に向けて、下方向にスクロールして
読むように書いてあります。
念のためいつも、その時点で販売中か秋月電子の通販サイトで確認してから紹介の返信していましたが、2017年7月に
確認したところ、代替品のキットも販売終了の様でした。
そんなわけで、これからの対応を考えたのが、Pen4号久々の製作日誌更新のきっかけです。
一応記事に近い形で作れる、H8/3067の代わりにH8/3069を使う「マイコンボードキットの代替」記事を2007年から
2008年にかけて公開していますが、H8シリーズが全体的に補用品扱いになってきている様なので、「マイコンボードキットの代替2」
として2017年現在の一般的なマイコンを使った記事の検討を始めました。
手元にあった当時のキット付属のCDからインストールを試みたところ、インストーラーが途中までしか動きませんでした。
念のため新たに 通販コード S-00033として売っている方を新たに買って試してみたところ、インストールからコンパイルまで
行けました。
原因は調べていませんが「使えるならば、まあいいか」ということにしました。
こちらは自動アップグレードがうまく働き、警告は出ましたが実行まで行けました。
と言う事で記事が2つに分かれていて、我ながら分かりにくいなーと思いましたが、まだ有効だと確認できました。
あとで良く調べたところ、原稿を書いたときに残した、すべての要素がそろったプロジェクトフォルダからのアップグレードは
成功しましたが、サポートサイトのsource_code.zipに含めているソースコードはVB2010までは読み込みOKで、2017では
うまく行きませんでした。
そういった次第で、本記事のsource_code_171111.zipには、VB2015/2017で読み込みテストをしたコードを
含めました。(2017/11/29追記)
「マイコンボードキットの代替2」の記事の書き方としては、図面の見方や工作方法について、本で説明している
ことは了解済として、代替工作に関連する事をそれぞれの章についての追加説明の形で書く方針としました。
I/Oボード全体回路図2017/08/27版
備考
マイコンのハードにアクセスする関数は、マイコンの機種も、ソフトウェア開発環境も違うので大きく変わりましたが、
その他の部分は、例えばRCサーボの位置を指定する数字を変える等の小変更で済みました。
とりあえず見込みと違わず良かったです。
I/Oボード全体回路図2017/09/03版
オプション扱いの操縦システム用Xbeeを外し、ピン番号の重複などを直してブラッシュアップしました。
マイコンボードは「mbed NXP LPC1768評価キット」7000円で、こちらも秋月電子で購入しました。
はじめて買ったときは5500円くらいだった気がします。為替相場により変わるようです。
mbed NXP LPC1768評価キット
P84の写真に相当する写真
マイコンボードが小さくなったのでフリースペースが増えました。
そんなわけで、「6日目」に相当する作業は無くなりました。楽になっていいですね!
ちなみにケーブルは「mbed NXP LPC1768評価キット」に付いてきますが、ロボットを歩かせる
事を考えて、同じ規格(USBケーブル Aオス−ミニBオス)で2mくらいの長さの柔らかい物を用意
するのが良いでしょう。
代わりと言っては何ですが、PCとマイコンボードの間で仮想COMポート経由のシリアル通信(本書で元から使っているやりかた)
で通信するために、「mbed Windows serial port driver」をmbedのサイトからダウンロードしてPCにインストールします。
参考URL:
https://developer.mbed.org/users/weed/notebook/how_to_communicate_by_serial_port_on_windows7/
https://developer.mbed.org/handbook/Windows-serial-configuration
動作確認としては、PCにマスストレージデバイスとして認識され、Windowsのデバイスマネージャで追加されたCOMポートを確認できればOKです。
参考URL:「mbedを始めましょう!」という日本語のページでLPC1768とmbedを使い始めるまでのステップが易しく説明されています。
コンパイラ自体や画面のメニューはバージョンアップしていて少し違いますが、十分参考になるでしょう。
https://developer.mbed.org/users/nxpfan/notebook/lets_get_started_jp/
プログラミング
プログラミングは以下の流れとなります。LPC1768というマイコン用には本で使ったH8用コンパイラの様に、
レジスタの設定から入るタイプもありますが、mbedはその辺を省略できるので、ここ数年?電子工作で一般的な
Arduino IDEと同じ様な感じで使えます。
すると、マスストレージデバイスとして見えているエリアからマイコン内部のメモリーにプログラムがコピーされた後実行が始まります。
ちなみにここでは、電源はUSB経由でPCからとるため電池接続不要です。
P123の動作確認。
点滅させるLEDはマイコンボード上のものを利用しています。(写真の下側のLED)
ソースコード(タクトスイッチ操作でLED点滅)
#include "mbed.h" //マイコンボード上に4個並んだLEDの1つをled1の名前で定義 DigitalOut led1(LED1); //P30ピンを入力、プルアップ設定有 でsw3という名前に設定 DigitalIn sw3(p30, PullUp); int main() { //無限ループ開始 while(1) { //SW3の状態をチェックする if(sw3 == 1) { //スイッチが押されていない時はLED1消灯 led1 = 0; } else { //スイッチが押されている時LED1点灯 led1 = 1; } } } |
ソースコード(タクトスイッチ操作でLED明るさ変化させる)
#include "mbed.h" //マイコンボード上に4個並んだLEDの1つをled1の名前で定義 DigitalOut led1(LED1); //P30ピンを入力、プルアップ設定有 でsw3という名前に設定 DigitalIn sw3(p30, PullUp); //タイミングループ用タイマー Timer timer; int main() { //無限ループ開始 while(1) { //タイマーのカウントをクリア timer.reset(); timer.start(); //LED1点灯 led1 = 1; //SW3の状態をチェックする if(sw3 == 1) { //スイッチが押されていない時は短めにLED1点灯 while(timer.read_us()<4000); } else { //スイッチが押されている時は長めにLED1点灯 while(timer.read_us()<16000); } //LED1消灯 led1 = 0; //20.0m sec経過するまで待つ while(timer.read_us()<20000); } } |
Timerクラスは計測する時間をm secやμ secで与えられるので、マイコンのクロック周波数から計算する
過程が無くなり簡単になりました。
電池は2005年頃から使っているSONYのハンディーカム用の7.2Vのリチウムイオン電池です。
執筆していた頃のニッカド電池やニッケル水素電池は使えなくなってしまいましたが、この電池は
まだ使えるので、撮影に使いました。
ソースコード(タクトスイッチ操作でRCサーボを動かす)
#include "mbed.h" //マイコンボード上に4個並んだLEDの1つをled1の名前で定義 DigitalOut led1(LED1); //GPIOの25番をsm13という名前で出力に設定 DigitalOut sm13(p25); //P30ピンを入力、プルアップ設定有 でsw3という名前に設定 DigitalIn sw3(p30, PullUp); //タイミングループ用タイマー Timer timer; int main() { //無限ループ開始 while(1) { //タイマーのカウントをクリア timer.reset(); timer.start(); //sm13を1にセット sm13 = 1; //SW3の状態をチェックする if(sw3 == 1) { //スイッチが押されていない時はパルス幅を1.5m secにする while(timer.read_us()<1500); } else { //スイッチが押されている時はパルス幅を2.0m secにする while(timer.read_us()<2000); } //sm13を0にセット sm13 = 0; //20.0m sec経過するまで待つ while(timer.read_us()<20000); } } |
I/Oボード全体回路図2017/09/17版
配線作業の途中で、配線の長さがより短くなるようブラッシュアップしました。
使うときもVisual Studio 2017として起動し、プロジェクトを定義するときにVisual Basicを選ぶ形に
なっていました。
P208の画面はこんな感じになり、
「Visual Basic -> Windows クラシック デスクトップ->Windows フォームアプリケーション(.Net Framework)」
の順で選択した後、新しいプロジェクトの「名前」としてpen4_vbを入力して始めます。
P224辺りでソースコードを分割していますが、9〜10日目あたりで触れたように、レジスタ設定に関するコードが
無くなります。そのためinit.cは書くことが無いので省略し、pen4.hはだいぶ簡単になりました。
プログラムの流れや構成は変えず、マイコンのハードウェアに関わる部分を書き換えました。
その他、ジャイロはRCサーボと同じPWMパルスを入出力するPG-03から、本の出版よりあとでスマートフォンの部品として
一般化したI2C接続タイプに変更するので、試しに動かしてみるコーナーは読むだけとなります。
pen4.h
/**************/ /*構造体の宣言*/ /**************/ /*シリアル通信関連構造体*/ struct SIO { unsigned char command; /*コマンドキャラクタ*/ }; /*RCサーボ関連構造体*/ struct SERVO_DATA { unsigned char switch_signal; /*制御信号スイッチ*/ unsigned int data_test[4][4]; /*テスト用出力データ*/ }; /************************/ /*関数のプロトタイプ宣言*/ /************************/ /*sci0.c に含まれている関数*/ void sci_write_char(unsigned char data); void sci_write_uint(unsigned int data); unsigned char sci_read_char_wait(void); unsigned char sci_read_char_no_wait(void); unsigned int sci_read_uint(void); void sci_print_string(char string[80]); void sci_print_int(int data); /*sci1.c に含まれている関数*/ void sio_comm1(struct SIO *sio, struct SERVO_DATA *servo_data); |
pen4.cpp
#include "mbed.h" #include "pen4.h" //マイコンボード上に4個並んだLEDの1つをled1の名前で定義 DigitalOut led1(LED1); //GPIOの25番をsm13という名前で出力に設定 DigitalOut sm13(p25); //P30ピンを入力、プルアップ設定有 でsw3という名前に設定 DigitalIn sw3(p30, PullUp); //タイミングループ用タイマー Timer timer; //SIO Serial pc(USBTX, USBRX); int main() { unsigned char i, j; struct SIO sio; struct SERVO_DATA servo_data; /*ハードの初期化を行う*/ pc.baud(19200); //RCサーボ出力データの初期化を行う for(j = 0; j < 4; j++) { for(i = 0; i < 4; i++) { servo_data.data_test[i][j] = 1500; } } //制御信号出力スイッチをONで初期化 servo_data.switch_signal = '1'; //無限ループ開始 while(1) { //タイマーのカウントをクリア timer.reset(); timer.start(); //制御信号出力スイッチがONの場合信号を出力する if(servo_data.switch_signal == '1') { //モニタとしてLED点灯 led1 = 1; //sm13を1にセット sm13 = 1; /*PCからの受信データに従った時間待つ*/ while(timer.read_us() < servo_data.data_test[1][3]); //sm13を0にセット sm13 = 0; } else { /*LED消灯*/ led1 = 0; } /*コマンドが受信されているか確認する*/ sio.command = sci_read_char_no_wait(); /*コマンドが受信されていた場合は、コマンドに従った処理を行う*/ if(sio.command != 0) { sio_comm1(&sio, &servo_data); } /*1/50秒経過するまで待つ*/ while(timer.read_us()<20000); } } |
sio0.cpp
#include "mbed.h" #include "pen4.h" extern Serial pc; /*******************************************************/ /*unsigned char型変数1バイトをシリアルポートへ出力する*/ /*引数:data 出力する変数 */ /*返値:なし */ /*******************************************************/ void sci_write_char(unsigned char data) { while (!pc.writeable()); pc.putc(data); return; } /***********************************************/ /*unsigned int型変数 をシリアルポートへ出力する*/ /*引数:data 出力する変数 */ /*返値:なし */ /***********************************************/ void sci_write_uint(unsigned int data) { unsigned int ii; unsigned char data_L, data_H; /*unsigned intのデータをunsigned charのデータ2個に分割変換する*/ if(data > 255) { ii = data / 256; data_H = (unsigned char)ii; } else { ii = 0; data_H = 0; } ii = data - ii * 256; data_L = (unsigned char)ii; /*データを出力する*/ sci_write_char(data_H); sci_write_char(data_L); return; } /*************************************************************/ /*unsigned char型変数(1バイト)をシリアルポートから入力する*/ /*備考:データが来るまで待つ */ /*引数:なし */ /*返値:unsigned char型データ */ /*************************************************************/ unsigned char sci_read_char_wait(void) { unsigned char data; while(!pc.readable()); data = pc.getc(); return(data); } /*************************************************************/ /*unsigned char型変数(1バイト)をシリアルポートから入力する*/ /*備考:データが来ていなければなにもせず帰る */ /*引数:なし */ /*返値:unsigned char型データ */ /*************************************************************/ unsigned char sci_read_char_no_wait(void) { unsigned char data; data = 0; /*受信レジスタにデータがあるかチェックする。無ければ帰る*/ if(pc.readable()) { data = pc.getc(); } return(data); } /************************************************************/ /*unsigned int型変数(2バイト)をシリアルポートから入力する*/ /*備考:データが来るまで待つ */ /*引数:なし */ /*返値:unsigned int型データ */ /*************************************************************/ unsigned int sci_read_uint(void) { unsigned int data; unsigned int data_L, data_H; /*unsigned charのデータ2個を受け取る*/ data_H = (unsigned int)sci_read_char_wait(); data_L = (unsigned int)sci_read_char_wait(); /*unsigned intのデータに変換する*/ data = data_H * 256 + data_L; return(data); } /************************************************************/ /*unsigned char型変数80バイト max をターミナル画面に表示する*/ /*引数:string 表示する文字列 */ /*返値:なし */ /************************************************************/ void sci_print_string(char string[80]) { unsigned char i; /*受け取ったデータをターミナル画面に表示する*/ i = 0; do { sci_write_char((unsigned char)string[i]); i++; } while(string[i] != 0 && i < 80); /*文字列の終わりが = でなければ改行する。 = の場合は 次回の文字列が同じ行に連なる。変数の内容表示などに使う*/ if(string[i - 1] != '=') { /*改行コードを送信する*/ sci_write_char(0x0d); sci_write_char(0x0a); } return; } /*************************************************/ /*int型の整数をターミナル画面に表示する */ /*引数:data 表示する整数 */ /*返値:なし */ /*************************************************/ void sci_print_int(int data) { unsigned int i, j, bitptn; char string[16]; /*dataの正負を判断し符号を付ける*/ if(data < 0) { string[0] = '-'; data = data * -1; } else { string[0] = ' '; } /*dataを文字列に変換する*/ bitptn = 10000; for(i = 1; i < 6; i++) { j = data / bitptn; string[i] = '0' + j; data = data - bitptn * j; bitptn = bitptn / 10; } /*意味のないゼロをスペースと置き換える 例 00010 → 10*/ for(i = 1; i < 5; i++) { if(string[i] == '0') string[i] = ' '; else break; } /*文字列の終わりの位置に0をセット*/ string[6] = 0; /*文字列をターミナル画面に表示する*/ sci_print_string(string); return; } |
sio1.cpp
#include "mbed.h" #include "pen4.h" /*************************************************/ /*PCから受け取ったコマンドに沿って通信を行う */ /*引数:SIO構造体へのポインタ */ /* SERVO_DATA構造体へのポインタ */ /*返値:なし */ /*************************************************/ void sio_comm1(struct SIO *sio, struct SERVO_DATA *servo_data) { unsigned char i, j; /*RCサーボの制御信号 出力ON/OFF命令受信*/ if(sio->command == 'A') { /*受信準備完了*/ sci_write_char(sio->command); /*スイッチデータ受信*/ servo_data->switch_signal = sci_read_char_wait(); } /*RCサーボのテスト用出力値受信*/ if(sio->command == 'B') { /*受信準備完了*/ sci_write_char(sio->command); /*出力値データ受信*/ for(j = 0; j < 3; j++) { for(i = 0; i < 4; i++) { servo_data->data_test[i][j] = sci_read_uint(); /*手足腰(0〜11番) */ } } servo_data->data_test[0][3] = sci_read_uint(); /*首(12番) */ servo_data->data_test[1][3] = sci_read_uint(); /*テスト端子(13番) */ } } |
具体的には、P278の部品図中、番号6の真ん中あたりにある直径12mmの穴を移動すると共に、
ケーブルを固定するための部品22〜24の形と取り付け場所も、適当に対応させます。
関連することですが、電池電圧を最高9V程度まで考慮して、I/Oボードの回路図中で電池電圧の
測定回路(R2とR3)の抵抗値を変更しています。
それぞれのセンサキットの品名は以下の通りで、両方とも秋月電子のキットです。
基板裏側
基板の端に、2mm角プラ棒を貼り付けてあります。ここに両面テープを張り付けて、基板裏面をフレームから
2mm浮かせて取り付けます。
取り付け状態
基板の端に、2mm角プラ棒を貼り付けてあります。ここに両面テープを張り付けて、基板裏面をフレームから
2mm浮かせて取り付けます。
回路図
本作例では、通常I2Cのラインに取り付けるプルアップ抵抗について、AE-LIS3DH内にあるプルアップ抵抗
を利用しています。そのため本回路図には書いてありませんので注意してください。
pen4(マイコン用プログラム)
ついでにディップスイッチを付けておくと、自律モードで動かすときのモード切替等に使えて、これも便利です。
調整作業
sio1.cppより抜粋
//Battery用アナログ入力 AnalogIn battery(p20); |
/*電池残量表示*/ if(sio->command == '8') { x = battery * 3.3; y = x / (1.0 / (1.0 + 2.2)); /*ターミナルに電圧値を表示する*/ printf("Battery Input = %4.1f V\n", y); } |
「LPC1768にはRAMが64KB付いており、AKI-H8/3067の倍あるから余裕があるか・・・」と思っていましたが、簡単に調べてみると
64KBのうち32KB以上エリアをmbedに予約されていることが分かりました。
システム予約エリアを転用してしまうと、後々コンパイラがバージョンアップした時などに動かなくなったり等、
支障がでることが考えられるので、ここはとりあえずメモリーが足りる分だけモーションデータを生成し、ロボットを
動かすように制限するに留めました。
本の記事と対比させながら製作を進めるのは、ロボットの基本部分が完成する45日目までと考えており、残りも
あと少しなので、そこまで進めたの後に別途検討する事にしました。
案としては、Pen4 Ver3で採用したI/Oボード上に追加したSDカードにモーションデータを記録する方法と、動作中は
有線でPCから送る方法を紹介しようかな〜と検討しています。
測定値を得た後の処理は、入ってくる数字の範囲が変わる(30%程小さくなる)こと等を、フィードバックのゲイン
の数値で吸収したりの小変更以外は、ほぼ元のままです。
あとは、とりあえず「43日目」のところで分かったメモリー不足で旋回のモーションデータが読み込めなかった件の
対策について、何か考えて公開しようと考えています。
片方は定格6.6V 1100mAhで元の電池と交換可能そうなものを選びました。もう片方は定格9.9V900mAh
で、大きさ的には元の電池に近いですが、DC-DCコンバータを挟んで電圧を下げて使う必要がある機種です。
搭載準備の第一歩として、買う前に寸法は調べてありますが、どんな感じか実際載せてみました。
大きさ比較の写真です。参考に単3型エネループも置いてみました。
大きさに関して、見込みどおりであることが確認できました。
しかしその後、モーションデータ用エリアの取り方を変えたところ動くようになりましたので、このプログラムについては「44日目のプログラム
のバージョン2」として公開する事としました。(これまでのソースコードも、これはこれで参考になるかと思い、残しておくことにしました。)
ちなみに何を変えたかと言いますと、元はRCサーボに出力するデータについて、モーションデータを記憶しているエリアへの
ポインタをずらして間接参照により取り出す方式を採用していました。この方式は計算上ではまだメモリーに余裕があるはず
なのに、データを増やしてゆくと働かなくという現象に出会いました。
元はH8の処理能力不足を補う目的で取っていた方法なので、マイコン代替えで出た余裕を利用し、直接参照
に変えてみたところ、こちらはうまく働きました。
2017/11/11版ソースコード:ダウンロード
普段は5Vで動かしているので、それと比べると関節の保持力が上がって硬くなった印象です。
今日のところは、特にRCサーボが壊れることもなく動かせました。
具体的なエラー内容としては「charで宣言したクラスの引数としてconst charを渡してはいけない」というものでした。
対応策の参考にMicrosoftのVisual Studioのドキュメントページなどでちょっと読んでみたところ、C++の標準の更新に合わせて、 コンパイル時のエラーチェックが厳しくなったということの様でした。そこで以下を試してみました。
教材的な観点からは前者の「なるべく新しいC++言語の標準に沿う形に直す」という方が望ましい気もしましたが、 記事とサポートページのソースコードに違いがあるもの混乱の元になると考えたので、後者の方をサポートページに 紹介することとしました。