RC 受信機をつなぐ

(2003年 10月 27日 (C) 岡田 紀雄)

吉野のロボットさんなどで紹介されているRC送受信機のロボットへの接続方法を自分なりに解釈し、作ってみた回路(と呼べるほどのものではない)と、プログラムについて紹介する。


回路編

はじめて WinDraft Schematic Caputure を使って回路図なるものを書いてみたので、書き方の間違いなどあるかと思うが、覚書きでもあり、ピン番号(名称)を書いておけば、後から読んでも何とかなる、と信じてこれを完成版とする。 ちなみに、WinDraft は 80pin までならフリーで使える試供版がインターネットで入手できる(使用可能ピン数は、バージョンによって変わるようだ。 2003年10月時点では、Version 3.12 (Nov. 2002))。
至って簡単な回路図なので、半田ごて握ってチョコッと工作をしたことのある方なら、コピーできると思う。 ただ、私もそうだったのだが、単に接続すれば良いというレベルでも、最初は怖いもの。

今回、使用した各パーツは、下記のとおりで、いずれも秋葉原で入手可能。

結線が終わったところだが、1つ私が行ったことを追記しておく。
H8/3664 Flash メモリへのプログラム書き込みは、上記図面の SP3232 と H8/3664 とを簡単にケーブル交換できるようなコネクタを入れている。 どうしても、マザーボードのプログラムを作っている際に、H8/3664 側のプログラムも変更したいということがあると考えたためでもある。


H8/3664 プログラム

簡単なプログラムではあるが、吉野のロボットさんのところの解説や RoboOne カンファレンスの OHP シートだけでは理解できなく、ギブアップしてしまいそうなところでもある。 そこで、どうして最終的なプログラムに行き着くことができるのか? というのを順を追って(私がやったのと同じ手順で)書いておく。 まぁ、個人的なメモなので、これで十分かと。。。


第1段階:まずは、RC 受信機からの信号を読み取ってみる。
少なくとも、RC受信機からの信号パターンは PWM であるということはいろいろなところで書かれているので、知っている、というのが前提でした。 そこで、各チャンネルを同時に読み取り、信号が ON になる時刻と、OFF になる時刻を出力するプログラムを書いた。

1:      #include        "3664f.h"
2:      void            out1h( unsigned char ch )
3:      {
4:              ch &= 0x0F;
5:              ch = ( ch > 9 ) ? (ch - 10) + 'A' : ch + '0';
6:              while( SCI3.SSR.BIT.TDRE == 0 ) ;
7:              SCI3.TDR = (short)ch;
8:              SCI3.SSR.BIT.TDRE = 0;
9:      }
10:     void            out2h( unsigned char xx )
11:     {
12:             out1h( xx >> 4 );
13:             out1h( xx );
14:     }
15:     void            main( void )
16:     {
17:                                             /* PDR5 は P57-P50 I/O port の入力レジスタ      */
18:                                             /* PDR5の上位ビットが P57, 下位ビットが P50     */
19:             IO.PMR5.BYTE = 0x00;            /* PDR5(P57-50)の全ビットを汎用入出力モード     */
20:             IO.PCR5 = 0x00;                 /* PDR5(P57-50)の全ビットを入力モード           */
21:             IO.PUCR5.BYTE = 0x00;
22:             TW.TCRW.BIT.CKS = 3;            /* クロックを8分週にセット                     */
23:             TW.TMRW.BIT.CTS = 1;            /* TW Start(フリーランニングモード)           */
24:             while( 1 ) {
25:                     out2h( IO.PDR5.BYTE );
26:             }
27:     } 

結果、チャンネル1のON → OFF が終了し、その後、チャンネル2,3,4、1,2,・・・の順番に ON → OFF が繰り返されていくことがわかった。

つまり、1バイトで入力できる形で回路設計したので、P50(下位)...P53(上位)の4ビットにて上記波形を入力すると、
  0x00 0x00 0x01 0x01 0x02 0x02 0x02 0x02 0x02 0x04 0x08 0x08 0x08 0x00 ...
という情報が得られることになる。 各ビットのみの情報を得るには、上記読み取り値に論理積(and) をとることで任意のビットの変化を知ることができる。


第2段階:ON 時間の測定方法は内臓タイマで計測する。
基本プログラムは、吉野のロボットさんのところにあるので、それを流用させていただく。
そこにも書かれているが、非常にCPUを無駄に使っているが、私も専用CPUをあてがっているので、特に気にしないで使うことにした。

1:      #include        "3664f.h"
2:     void            initio( void )
3:     {
4:             IO.PMR5.BYTE = 0x00;            /* PDR5(P57-50)の全ビットを汎用入出力モード     */
5:             IO.PCR5 = 0x00;                 /* PDR5(P57-50)の全ビットを入力モード           */
6:             IO.PUCR5.BYTE = 0x00;
7:             TW.TCRW.BIT.CKS = 3;            /* クロックを8分週にセット                     */
8:             TW.TMRW.BIT.CTS = 1;            /* TW Start(フリーランニングモード)           */
9:     }
10:     unsigned int    get_reciver( unsigned char ch )
11:     {
12:             unsigned int    sc, ec;
13:                                             /* PDR5 は P57-P50 I/O port の入力レジスタ      */
14:                                             /* PDR5の上位ビットが P57, 下位ビットが P50     */
15:             while( (IO.PDR5.BYTE & ch) != 0 ) ;
16:             while( (IO.PDR5.BYTE & ch) == 0 ) ;
17:             sc = TW.TCNT;
18:             while( (IO.PDR5.BYTE & ch) != 0 ) ;
19:             ec = TW.TCNT - sc;
20:             return ec;
21:     } 

流用させていただいたプログラムだが、何故だかカウンタクリアのコードを入れると、そこで止まってしまうので、私は第1段階と同じく、開始時刻と終了時刻を読み取り、それらの差をRC受信機からのパルス幅として認識するようにした。 うまくカウンタクリアができるようになれば、変更すればよい。


第3段階:受け取ったRC受信機からの信号をマザーボードへ転送する。
ここは特に難しくないだろう、と思ったが、意外とはまってしまった(未だに原因不明のままである)。
4チャンネルあるので、チャンネル識別コードとして、アルファベットのw,x,y,z をヘッダとしていれて、パルス幅を16ビットコードで渡そうとした。 もちろん、アスキー化しておいた方が何かとデバッグも簡単なのでそうしているが、ヘッダとして入れている文字が2回出力されてしまう。 このあたりの基本コードは H8/3664 開発キットのサンプルプログラムからの転用で、別に小細工はしていないはず。 とりあえずは、動いているので、そのままとする。
ここで、16ビットコードを16進数のアスキー変換をしても良かったが、0〜9,A〜F と非連続アスキーコードに変換するわずらわしさ、また、マザーボード側で受け取る際にも、逆変換するのが面倒なことから、16進数をアスキーコードが連続な A〜P に変換することにした。 そのため、ソースコードがすっきりしている。

1:      #include        "3664f.h"
2:      /*      out (RS232C)    */
3:      void            outch( unsigned char ch )
4:      {
5:              while( SCI3.SSR.BIT.TDRE == 0 ) ;
6:              SCI3.TDR = (short)ch;
7:              SCI3.SSR.BIT.TDRE = 0;
8:      }
9:      void            out4h( unsigned short xx )
10:     {
11:             outch( ( ( xx >> 12 ) & 0x000F ) | 'A' );
12:             outch( ( ( xx >> 8  ) & 0x000F ) | 'A' );
13:             outch( ( ( xx >> 4  ) & 0x000F ) | 'A' );
14:             outch( (   xx         & 0x000F ) | 'A' );
15:     }
16:     void            crlf( void )
17:     {
18:             outch( 0x0D );
19:             outch( 0x0A );
20:     }
21:     /*      initialize I/O (w/ RS-232C)     */
22:     void            initio( void )
23:     {
24:             short           xx;
25:     
26:             IO.PMR1.BIT.TXD = 1;
27:             SCI3.SCR3.BYTE = 0;             /* clear all flags                              */
28:             SCI3.SMR.BYTE = 0;              /* Ascnc, 8bit, NoParity, (Even), stop1, 1/1    */
29:             SCI3.BRR = 25;                  /* 19200baud (CPU=16MHz)                        */
30:             for( xx = 0; xx < 280; xx++ );  /* wait 1 bit time (1/9600 sec)                 */
31:             SCI3.SCR3.BYTE = 0x30;          /* scr=0011 0000 (TE=1,RE=1)                    */
32:             xx = SCI3.SSR.BYTE;             /* Dummy Read                                   */
33:             SCI3.SSR.BYTE = 0x80;           /* Clear Error Flag (TDRE=1)                    */
34:                                             /*      RC受信機接続ポートの初期化            */
35:             IO.PMR5.BYTE = 0x00;            /* PDR5(P57-50)の全ビットを汎用入出力モード     */
36:             IO.PCR5 = 0x00;                 /* PDR5(P57-50)の全ビットを入力モード           */
37:             IO.PUCR5.BYTE = 0x00;
38:             TW.TCRW.BIT.CKS = 3;            /* クロックを8分週にセット                     */
39:             TW.TMRW.BIT.CTS = 1;            /* TW Start(フリーランニングモード)           */
40:     }
41:     /*      RC信号読み取り部      */
42:     unsigned int    get_reciver( unsigned char ch )
43:     {
44:             unsigned int    sc, ec;
45:                                             /* PDR5 は P57-P50 I/O port の入力レジスタ      */
46:                                             /* PDR5の上位ビットが P57, 下位ビットが P50     */
47:             while( (IO.PDR5.BYTE & ch) != 0 ) ;
48:             while( (IO.PDR5.BYTE & ch) == 0 ) ;
49:             sc = TW.TCNT;
50:             while( (IO.PDR5.BYTE & ch) != 0 ) ;
51:             ec = TW.TCNT - sc;
52:             return ec;
53:     }
54:     void            main( void )
55:     {
56:             initio();
57:             while( 1 ) {
58:                     unsigned int            gr;
59:
60:                     crlf();
61:                     gr = get_reciver( 1 );  outch( 'w' );   out4h( gr );
62:                     gr = get_reciver( 2 );  outch( 'x' );   out4h( gr );
63:                     gr = get_reciver( 4 );  outch( 'y' );   out4h( gr );
64:                     gr = get_reciver( 8 );  outch( 'z' );   out4h( gr );
65:             }
66:     }      

第4段階:マザーボードでRC操作コードを受け取る。(もちろんモーションへ反映させるが、そこは割愛)
今回、BTC050 のプログラム等送受信するための SCI1 ポートは使わず、使っていないもう1つのポート SCI0 を使用した。 BTC050 には BTE055 をつけているため、BTC050 の下へはSCI0 は引き出せないので、上へ引き出した。 3段重ね状態である。 実際に、その3段目のボードに、上記回路のSP3232 関連を乗せている。 受信プログラムは下記のとおり。

1:
2:      static  unsigned char   rc_buf[ 26 ];
3:      static  unsigned char   *rcbp;
4:      int             rc_ch1, rc_ch2, rc_ch3, rc_ch4;
5:
6:      static  int     change_str2int( unsigned char *bp )
7:      {
8:              int             i;
9:
10:             i  = ( *bp - 'A' ) & 0x0F;
11:             i <<= 4;
12:             bp++;
13:             i |= ( *bp - 'A' ) & 0x0F;
14:             i <<= 4;
15:             bp++;
16:             i |= ( *bp - 'A' ) & 0x0F;
17:             i <<= 4;
18:             bp++;
19:             i |= ( *bp - 'A' ) & 0x0F;
20:             return i;
21:     }
22:     void            read_rc( void )
23:     {
24:             if( port0.rs_buff() ) {
25:                     *rcbp = port0.rs_getc();
26:                     rcbp++;
27:                     if( rcbp >= rc_buf + sizeof( rc_buf ) ) {
28:                             rcbp = rc_buf;
29:                             rc_ch1 = change_str2int( &rc_buf[ 2      ] );
30:                             rc_ch2 = change_str2int( &rc_buf[ 2 +  6 ] );
31:                             rc_ch3 = change_str2int( &rc_buf[ 2 + 12 ] );
32:                             rc_ch4 = change_str2int( &rc_buf[ 2 + 18 ] );
33:                     }
34:             }
35:     }
36:     void            init( void )
37:     {
38:             // シリアル(SCI0)初期化 19200[bps] for 3664 with RC
39:             SCI0_INIT( br19200, 3, txb0, sizeof( txb0 ), rxb0, sizeof( rxb0 ) );
40:             // データ末尾を探し先頭よりデータが取れるようにする
40:             while( port0.rs_buff() ) {
41:                     char            c;
42:
43:                     c = port0.rs_getc();
44:                     if( c == 0x0A ) {
45:                             break;
46:                     }
47:             }
48:             rcbp = rc_buf;
49:     } 

SCI1 でのコードは、BTC050 に添付されたプログラムなどであるとおりなので、そのプログラムを SCI0 へ書き換えれば良いだけ。 あと、アスキー変換されてしまっている数字を元の数値に直すという処理が入るだけのもの。 吉野のロボットさんなどで紹介されている応用方法では、RC送信機のスティック位置を2値化してつかってしまっているが、あえて、アナログな状態(とはいえ、PWMで読み取る際に離散的に飛んだ数値になってしまっている)のまま受信することで、モーションの詳細な制御がRC送信機からも可能となる。 もちろん、2値化してしまった状態での使用も可能なので、モーションデータをスタートさせるためだけのモードなど用意しておいて使うのも良いと思うが、まずは、アナログ的な操作を手に入れるため、後者は見送り。