センサーのでんげんが3.3Vなので、確かに、RaspberrPiからも取れるのだが、せっかく5V4アンペアの別電源を用意したのだから、そこから3.3Vを取り出そうと思って、DC-DCコンバーターM78AR033-0.5(380円)も買ってきた。取り付けたのだが、なんだか、ジャイロ用の電源が取り出せない。おかしいと思って、テスターで調べたら、なんと、極性が逆だった。元の、5Vから逆だったのだ。逆だったら、逆向きで降圧していた。ちょっと笑っちゃうね。
真ん中がデバイスで、裏側の配線で右の二つのピンに出しているが、プラスとマイナスが逆だった。
(その後、極性を正しく直した。ピンの左がGRNDで、みぎが3.3V。2017年4月22日)
RaspberryPiでサーボモーターを動かす
アキバで、サーボモーター(回転型)GWSS35STDを2機買ってきた。サーボモーターをチャンネルで動かすためのドライバボードPCA9685も買う。
モーターを動かすための別電源を用意した方がいいので、先日、RspberryPiの上に乗せるボードに5ボルト入力コネクタをつけれたので、これもまた先日買ってきた5V4アンペアの電源ユニットをつないで、そこから配線することにした。
写真の右端にコネクタはつけてそこからドライバボードにつなげている。繋げると、LEDの青が光る。
ボードはこちらからみて、右側にRspberryPiのピンからの入力を入れる。そこは半田付けをコレクタに、ピンを刺している。
ボードの上側に、サーボからの3線入力をさす。ここでは、0チャンネルと1チャンネルの二つを使って2台を制御する。全部で16台繋げるが、最初の4つのコネクタしか半田付けしなかった。
電源は、付いたものを半田付けしたが、ネジで差し込んだ線を固く止めている。LEDの左側が電源だ。
配線とプログラムは、
http://qiita.com/shigeru-yokochi/items/ac2138feb74a7ef7ffc6
を参照させていただいた。ほとんどそのまま使えます。RaspberryPi2のピン配置だが、ここでつなぐピンに関しては、RaspberryPi3と同じなので。
それだけです。
ツイッターに動画を上げています。
RaspberryPiからサーボモーター2台を2チャンネル使って動かす。5V、4アンペアのモーター用電源をコンピュータの上に重ねたボードに入れ、そこからサーボドライバに引っぱった。
ちゃっちいサーボも動く。#RaspberryPi #PCA9685#GWSS35STD pic.twitter.com/X7OKQvcHKZ
— わっしー教授(ロボット即興芸) (@wassiisg) 2017年4月20日
RaspberryPi 3からキャラクタディスプレイ(LCD)を制御する
RaspberryPi 3のGPIOピンをうまく使ってやろうと思った。いずれ、自分でロボットを作りたいという気持ちもあるので。さしあたっての試みだ。
LCDは、秋月電子で500円で売っていたACM0802C-NLW-BBH、8文字2行のちっちゃいやつだ。
最終的にうまくいったので、記録しておく。
(1)ハード作り
R7に付いている100オームの抵抗をつける。R8は何もしない。R9は、抵抗の切った線を使って直結する。抵抗は小さくつけたが、もう少し脚長でつけてもよかった。
右側に、ピンがさせるようにコネクタをつける。この写真では裏側になって半田付けしか見えないが。
10Kの半固定ボリュームも必需品だ(別売、秋月電子で買っておく)。つけないと表示されない。足三本が、三角に配置されたものを買った。足は三角の右から1,2,3となっている。半固定ボリュームのピン1をLCDのピンの1,2をピン3、3をピンの2に配置する感じになるはず(自分で確認すること)。足が短かったので、抵抗の線をつぎはぎして半田付けした。ショートに気をつける。
(2)結線
ピンを節約するために、4ビットモードにする。文字は、1バイトで作られているので、データを4ビットずつ2度送らないという面倒さはあるが、コンピュータ側のピンの節約の方が大事である。
結線は複雑にしない方が良いので、電源関係以外は、LCDのピン番号を、RaspberryPiのgpio番号に対応させるというポリシーにする。
電源:
LCD1→RSP2 (5.5V)
LCD2→RSP6 (GRD)
LCD3→半固定ボリューム2
制御:
LCD4→GPIO4 (RS) データ、コマンドの切り替え
LCD5→GPIO5 (RWは使わないので、GRD:どれでも良いがピン25にした)
LCD6→GPIO6 (E)
LCD11→GPIO11 (DATA)
LCD12→GPIO12 (DATA)
LCD13→GPIO13 (DATA)
LCD14→GPIO14 (DATA)
これで結線終わり。バックライトが光るはずだ。
(3)ソフトウェア
まず、以下のページを参考にして、WiringPiを使えるようにする。
https://tool-lab.com/2013/12/raspi-gpio-controlling-command-2/
このCライブラリを使わせていただき制御する。
基本的に、WiringPiを初期化し、LCDを初期化し、書き込む。ただし、パルスを送るタイミングが複雑で微妙なのだ。そこで、
http://amahime.main.jp/lcd/main.php?name=lcd
にPICのCでACM0802C-NLW-BBHを制御したプログラムが記載されているので、それを参考にさせていただきながらRaspberryPi用に書き換える。
まず、ライブラリ的な、liblcd.c である。
/*
キャラクタディスプレイACM0802C-NLW-BBHをRaspberryPi 3 から制御する
liblcd.c
2017年4月19日
*/
#include <wiringPi.h>
#include <stdio.h>
//LCD関数プロトタイピング
void lcd_init(void);
void lcd_str(char* str);
void lcd_data(char data, int cmd);
void lcd_out(char code, int flag);
// LCD 初期化
void lcd_init(void){
//
lcd_out(0x00, 1); // Displayをクリアしたい
//
delay(30); // 30ms
lcd_out(0x02, 1); // Function set
lcd_out(0x02, 1);
lcd_out(0x08, 1);
delayMicroseconds(39);// 関数作成する必要あり
lcd_out(0x00, 1); // Display ON/OFF
lcd_out(0x0C, 1); //
delayMicroseconds(39);
lcd_out(0x00, 1); // Display Clear
lcd_out(0x01, 1); //
delayMicroseconds(1530);
lcd_out(0x00, 1); // Entry Mode Set
lcd_out(0x06, 1); //
}
// 文字列出力
void lcd_str(char *str){
while(*str != 0x00){
lcd_data(*str,0); //文字出力
str++;
}
}
// dataは送信内容 cmd は[0] ならデータ、[1]ならコマンド
void lcd_data(char data, int cmd){
//printf("lcd_data 1 [ %c ] の書き込み開始 \n",data);
//printf("lcd_data 2 上位ビットの書き込み開始 \n");
lcd_out(data>>4,cmd);// 上位4ビット出力
//printf("lcd_data 3 下位ビットの書き込み開始 \n");
lcd_out(data, cmd);// 下位4ビット出力
if(cmd == 1){
if((cmd & 0x03) != 0) delayMicroseconds(50);
}else{
delayMicroseconds(50);
}
}
// ビットチェンジ及び信号制御
void lcd_out(char code, int flag){
int gpio11 = (code & 0x01);
int gpio12 = (code & 0x02);
int gpio13 = (code & 0x04);
int gpio14 = (code & 0x08);
//printf("lcd_out 1 [ %c ] の書き込み開始 \n",code);
digitalWrite(11, gpio11);
digitalWrite(12, gpio12);
digitalWrite(13, gpio13);
digitalWrite(14, gpio14);
//printf("lcd_out 2 データORコマンド\n");
if(flag == 0){
digitalWrite(4, 1);// データ
}else{
digitalWrite(4, 0);// コマンド
}
//printf("lcd_out 3 書き込み\n");
delayMicroseconds(50);
digitalWrite(6, 1);
//nanodelay(220); // 0 でいい
digitalWrite(6, 0);
//printf("lcd_out 4 書き込み終了\n");
}
次にメインモジュールである。
/*
キャラクタディスプレイACM0802C-NLW-BBHをRaspberryPi 3 から制御する
lcdtest.c
2017年4月19日
*/
#include <stdio.h>
#include <wiringPi.h>
#include <string.h>
char sendChar[] = "My n";
//char sendChar0[] = "LCD TEST";
//char sendChar1[] = "WASSIISG";
void lcd_init(void);
void lcd_str(char* str);
void lcd_data(char data, int cmd);
void lcd_out(char code, int flag);
int main(int argc, char** argv){
if(argc <= 1){
printf("表示すべき文字が指定されていません!\n");
return 1;
}
strcpy(sendChar, argv[1]);
printf("gpioを初期化します\n");
// Initialize WiringPi
if(wiringPiSetupGpio() == -1) return 1;
printf("ポートを出力モードにします\n");
// Set GPIO pin 4-14to output mode
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(11, OUTPUT);
pinMode(12, OUTPUT);
pinMode(13, OUTPUT);
pinMode(14, OUTPUT);
printf("少し待ちます\n");
delayMicroseconds(250);
printf("lcdを初期化します\n");
lcd_init();
printf("データを書き込みます\n");
int len = strlen(sendChar);
int i;
if(len > 8){
// 8文字より長い場合は
// 2行にまたがって記述する
for(i=0;i<8;i++){
lcd_data(0x80+i,1);
lcd_data(sendChar[i],0);
}
for(i=8;i<len;i++){
lcd_data(0xc0+i-8,1);
lcd_data(sendChar[i],0);
}
for(i=len;i<16;i++){
lcd_data(0xc0+i-8,1);
lcd_data(0x20,0);
}
}else{
for(i=0;i<len;i++){
lcd_data(0x80+i,1);
lcd_data(sendChar[i],0);
}
for(i=len;i<8;i++){
lcd_data(0x80+i,1);
lcd_data(0x20,0);
}
for(i=0;i<8;i++){
lcd_data(0xc0+i,1);
lcd_data(0x20,0);
}
}
printf("表示を終了しました\n");
//while(1){
//}
return 0;
}
色々苦労したが、最も苦労したのは、タイミングの管理である。なの秒単位の管理があるのだが、cのnanosleepだったかを使ったら、ものすごく時間がかかる表示になった。
delayMicroseconds
を使って治った。即表示するようになった。1μs以下のタイミングがあるのだが。0タイミングにした。
読み取った言葉をBNFファイル内容に対応させる
ALDialogやALSpeechRecognitionで言葉を読み取った時、不思議なことは、一致した言葉をBNFファイルの内容に対応させれば、topicファイルなどに書かれているそれに対するリアクションの言葉の内容がすぐに探し出せるのに、それをせずに、あらためてtopicファイルの中のruleに戻って調べなおしていることだ。こんな面倒なことをする理由がわからない。
たとえば、BNFファイルに、
<r410>:((やすみなさい)|(すわりなさい));
というトリガー(rule)があったとして、ろぼっとが「やすみなさい」という言葉を認識したとする。もちろん、この行があって認識できたのである。しかし、このトリガーとしての<r410>が情報として出されていれば、topicファイルのどの行に対応しているかはすぐわかるはずだ。わかるようにプログラムを組むことができるはずだ。それをしていないのである。ログを見ている限り、もう一度topicファイルから調べ直している。
理由はわからない。BNFをコンパイルしたlcfファイルがらみなのかもしれなない。
だから、自前のDialogでは、これを直接捉えたいと思った。ただ、bnfファイルは、プログラミング構文に使われる文法なので、使うためには構文解析アルゴリズムを使わなければならない。ただし、普通の数式処理構文解析プログラムならば、たくさんサンプルが出ていて、わかりやすく解説されているが、bnfを通してある言葉を認識し、それがbnfのどの構文に一致したのかを理解するアルゴリズムは、サンプルがない。もしかして誰もやっていない。
なので、1から作らなければならない。再帰下降構文解析法を応用しようと思っている。できたらここに掲載するつもりだが。できるのか。だいぶできているが複雑。
kakasiのインストール
英語モードと日本語モードを、「話」と「聴き」において自由に切り替える際に、日本語をローマ字に変換するため、kakasiをインストールした。つまり、opennaoでコンパイルしたということだ。これはそのままnaoに持ち込める。
kakasi -Ja -Ha -Ka -Ea -iutf8 -outf8
で、漢字、ひらがな、カタカナ、記号の全てをローマ字に変換する。たとえば、
kakasi -Ja -Ha -Ka -Ea -iutf8 -outf8
お題はコンビニです
odaihakonbinidesu
ALDialogの代替モジュール
ALDialogに変わるモジュールを作成することにした。完全なものでなくてもいい、私が必要な機能が全て含まれるものにしたい。言語の切り替えを、聞き取り、話し、のなかで自由に切り替えることが基本だ。Topicファイルも新しいものにするが、コンセプトやルールの基本的なものはそのままにしたい。私がほとんど使わない機能は組み込まない。だから、結構シンプルなものになると思う。
英語と日本語の混在
英語と日本語の混在としては、ALSpeechRecognitionでは、言語設定を日本語にして、ALTextToSpeechでは、言語設定を英語にする、あるいはその逆にしたりできれば良い。
ALSpeechRecognitionは、文脈ファイルに依存させるので、その言語は、ALSpeechRecognitionの言語設定と同じにしておかなければならない。これについては、かなり自由に操作できるようになった。その認識した言語に沿って、もう一方、トピックファイルには、そのリアクションも書かれているので、それを読み取って、speechrecognitionのイベント処理をやるメソッドを作成すれば良い。
まず、トピックファイルから、うまく、リアクション情報、すなわち喋る情報を取り出す部分を完成させよう。もう、ほぼ、頭の中では出来上がっている。
TopicファイルからBNFファイルへのコンバーター作成
基本ALDialog軸にやってきたが、英語日本語を混在させたりすると、ALDialogではうまく対応できないことがわかって、ALSpeechRecognitionレベルで対応することが便利になってきた。
そこで、どうしても必要になったのが、音声認識の文脈情報を与えているBNFファイルの作成だった。直接作成してもいいのだが、一番いいのは、qichatで書かれたTOPICファイルをBNFファイルに変換することだ。もちろん、これはNAOQIでやれる。ある意味、ALDialogを使っているうちに作成してくれるのだが、そのタイミングが理解できない。実に面倒なのだ。一旦作成してくれれば、ALSPeechRecongnitionで、コンパイルしてlcfというバイナリファイルを作成して、極めて効率よく、ロボットは聞き取りしてくれる。
昨日から今日にかけて、必死で作って、ようやく、まあ、そこそこ正しく作れたようだ。いくつか問題もあるが。ちゃんとロボットは作成したBNFファイルをコンパイルできていて、また、アクティベイトすると、正しく聞き取りしてくれる。
作成した、IbotBnf170328Japanese.bnfというBNFファイルをロボットに転送して、文脈に加えて、活性化させて、登録する。手順をpython SDKでやると以下のようになる。これで聞き取りできるようになった。
>>> from naoqi import ALProxy
>>> spr = ALProxy("ALSpeechRecognition","192.168.1.xx",9559)
>>> spr.compile("/home/nao/.local/share/dialog/IbotBnf170328Japanese.bnf","/home/nao/.local/share/dialog/IbotBnf170328Japanese.lcf","Japanese")
>>> spr.addContext("/home/nao/.local/share/dialog/IbotBnf170328Japanese.lcf","IbotContext")
>>> spr.activateAllRules("IbotContext")
>>> spr.pause(False)
>>> spr.getRules("IbotContext","active")
['IbotBnf170328Japanese#start', 'IbotBnf170328Japanese#take170320 trig']
>>> spr.subscribe("MySpeechRecognition")
>>> spr.unsubscribe("MySpeechRecognition")
聞き取った内容を、受け止めるのは、WordRecognizedのイベントを受け取ってやるので、それはまた別な作業なのだ。
日本語と英語のクロス
ロボットに日本語で問いかけると英語で答える、英語で問いかけると日本語で答えるようにしたいと思って、前回の記事のように、ALDialogでやろうとしたが結局破綻した。
そこで、もっとCoreなところにかえって、ALSpeechRecognitionでやったら、うまくいった。
結果はYoutubeに掲載した。
日本語と英語の会話をさせる
(この記事の方法は破綻した -> 参照)
日本語を聞き取って、英語を喋らせたり、英語を聞き取って日本語を喋らせたいと思った。
意外と面倒だった。本当は簡単にできるのかもしれないが。ここまで得た知識でなんとかなりそうなので、記録しておく。
これをやらせるためには、日本語を聞き取った後で、すぐに、ALDialogのsetLanguageを実行させて、英語に切り替え、英語を喋らせるようにすれば良い。ただし、一つのtopicファイルには、英語か日本語のどちらかしかプログラム化できないので、日本語を聞き取った後に、英語のtopicファイルにある、ルールをイベントで起動させることが必要だ。
何れにしても、英語と日本語のtopicファイルを同時にloadTopicをさせなければならない。
ここで大事なことをいかに整理する。
(1)聞き取りのためには、$HOME/.local/share/dialog/ 以下にあるbnfファイル(文脈処理)が必要だが、これはシステムが作る(自分で書いてもいいようだが、何しろ、このフォルダはALDialogが管理しているので、あまりいじらない方がいいよと思う)。このシステムファイルは、コンパイルされて、lcfファイルに変換される。
(2)この文脈処理ファイル群は、ロボットがスタートして、ALDialogがロードされて以降は、ALDialogが管理しているようで、その後勝手に、削除したりするとエラーが報告される。
(3)今まで日本語をやっていたのに、新たに英語のファイルを付け加えると、いろいろトラブルが起きる。
(4)一旦、文脈ファイルができると、別なトピックファイルが組み込まれても、その変更だけになり、新たにbnfファイルが作成されたりしない。
(5)ALDialogには、compileALLというメソッドがあって、これは文脈ファイルつくるのだが、bnfファイルがないときは、それを作成するきっかけにもなる。
(6)何もなければ、すべてのトピックファイルがloadTopicでロードした後に、compileAllを実行するとうまくいくみたいだ。
(7)その後、setLanguageで言語を切れ変えれば良い。
(8)日本語と英語を切り替える上では、それぞれのトピックファイルが共通のトピックを持っているようにするべきだ。
何しろ、文脈ファイルが決定的に重要な意味を持っている。
(追記)上の方法でもうまくいかない。どうしても、日本語のBNFしかつくらないときは、次の方法をとる。
(1)$HOME/.local/share/dialog/ にあるファイル(bnfファイル)等を一旦全て削除する。
(2)ロボットを再起動する
(3)pythonのnaoqi sdkを起動する(PCでいい)。そして、以下のようにやる。
>>> from naoqi import ALProxy
# ALdialogのオブジェクトを取得
>>> dlg = ALProxy("ALDialog","192.168.xxx.xxx",9559)
# 日本語と英語のトピックファイルをロードする
>>> dlg.loadTopic("/home/nao/ibot/user/topic/test170323-enu.top")
'test170323'
>>> dlg.loadTopic("/home/nao/ibot/user/topic/test170323-jpj.top")
'test170323'
# コンパイルする
>>> dlg.compileAll()
これでできるはずである。あとは、bnfファイルを気にせずにやれば良い。というか、二度とbnfファイルを直接いじらない