ホーム

電子工作

腕ロボット

二足歩行ロボット

無線コントローラ

工作機械

電子工作動画

自作ソフト(C言語)

Webページ作成

Javascriptゲーム

FLASHとは!

自学自習のテキスト

その他

H8(3052)の完成形(20013年7、8月)
 二足歩行ロボットに搭載するマイコンをいじっているうちにH8(3052)のマザーの完成形なるものが 見えてきました。
 「よりコンパクトに、見やすく、操作しやすく」を考えて、その結果左の写真にあるような ものに落ち着きました。

 最初のものに比べると、格段に進歩しています。

1)電源スイッチ(下の最も左のスイッチ)をトグルスイッチに変えました。これにより電源のON、 OFFがしやすくなりました。

2)プログラム書き込みモードと実行モードの切り替えに内部に2回路を有しているトグルスイッチ (下の左から二つ目)を使いました。
 書き込みモードにするには、FWEを5Vに、MD2をGND にセットする必要があります。つまり2回路を同時に変えなければなりません。
それでこのスイッチを使えば2つの回路を同時に変えられるので、このスイッチを いじるだけで書き込みモードと実行モードを変更できます。

3)PCとの通信するためのコネクタをコンパクトな3ピンコネクタにしました (ボードの一番下の真ん中にある23と書いてあるもの)。
 PCとの通信はシリアル(RS232C)を使いますので、そのコネクタは9ピンあり、 でかいものです。しかし、実際に通信に使うのは9ピンのうちのわずか3ピンです。

 そこで通信ケーブルを加工して実際に使う3ピンだけのコネクタにしてその結果ボードの方も 3ピンのコネクタになりました。
 左のケーブルがその加工済みのケーブルです。
 ケーブルの左の端のコネクタがシリアルのコネクタです。
 マイコンボードのマニュアルには、「シリアルのコネクタの2、3、5番のピンを通信に使い、7、8番を ショートさせる。それ以外のピンは使わない。」とありました。
 左端のコネクタが分解できなかったので、7、8番のショートはケーブルの右端で行っています。
 
4)現在のモードが「書き込みモード」か「実行モード」かを判別できるように、LEDを つけました。
下の黄色のLEDがそうです。電源をONすると、赤のLEDが点灯しますが、そのとき、 黄色のLEDが点灯していると「書き込みモード」であり、黄色LEDが消えていると 「実行モード」ということにしました。

 ボードの右端にあるピンソケットとマイコンのピンを裏側で接続して、マイコンに仕事を させるためのケーブルを出します。

 電子工作に手を染めてから約5年にもなりますが、この頃になってやっといろんなことが 見えてきたような気がします。
 何も知らないところから独学で初めて、書物、ネットで調べながらここまできました。
 「めくら蛇に怖じず」とはよく言ったもので、自分なりにこれでいいんだと自己流でやってきました。
 しかし、ネットや書物での先人の書いていることを自己流に解釈してやってきましたが、やはり 先人の教えてくれていることはそれなりのものがあります。
 今更ながら頭が下がります。RIKIYAさんや吉野さんの凄さを感じます。
H8(3052)の検証(2009年8月〜)
 ここでお話しすることはrikiyaさんのWebサイトを読んで学習して、僕なりにアレンジしてやったものです。
rikiyaさんのWebサイトのアドレスは→ http://homepage1.nifty.com/rikiya/index.htm
rikiyaさんのものはH8(3048)について書いておられます。
 H8(3052)は3048とは内部コンパチでクロック数が16→25MHz、ROM容量が 128→512KB、RAM容量が4→8KBとかなりパワーアップしていますが、一部の手直しで同じように動きます。
 この一部の手直しというのは、CPUが16から25MHzとなったためにタイマーの設定し直しぐらいだろうと思っていましたが・・・。
 ここには一応の説明を挙げますが、より詳しくはrikiyaさんのWebサイトをご覧になってください。 rikiyaさんにはこの場を借りてお礼を申し上げます。
検証用の回路
検証用の回路を作りました。
左がそれです。LEDを8個点灯できるようにしたものです。 簡便に取り付けられるようにコネクタをつけてCPUボードに挿せるようにしています。 LEDマイナス側はまとめて一本の線の先をクリップ式にしてCPUボードのグラウンド端子につなぎます。
これをCPUボードに取り付けたものが右のものです。
下が検証用のプログラムです。このプログラムによってポート1につなげたLEDがすべて点灯すれば正常!
/*ポート1を出力に設定し、ポート1の8ピンすべてONで出力する。led0.c*/
#include <3048f.h> /*I/Oアクセス用インクルード*/
main() {
  P1.DDR = 0xff; //ポート1を出力用に設定
  while(1) {
   P1.DR.BYTE = 0xff; //ポート1すべてのビットを1にする
  }
}
 H8タイニーで理解して出来たことがH8(3052)でそのまま手直しなしで出来るとは限りません。
 基本的には同じ発想なのですが実際には「使えるポートは何か!」とか、またインクルードファイルが少し異なっているために 「今までのC言語プログラムの記述がそのまま動くか?」とか、1から見直しを行かなければなりません。
 明らかな違いは、「ピンの数が100本という劇的に増えています」、「CPUのクロック数が異なっています」。
 また、C言語プログラムをコンパイルしたものをRS232Cケーブルを通してCPUに送るのですが、その送るツールがH8タイニー(3664)では DOSプロンプトから「Hterm」を使ってコンパイル後のABSファイルを送るのに対して、H8(3052)では「H8WriteTurbo」という GUIツールを使ってABSファイルをさらに変換したMOTファイルを送ります。
コンパイル作業の見直し
 この機会にC言語プログラムをコンパイルするためのバッチファイルを作って作業を簡略化しようと考えました。
 実はいままで時間がないために「とりあえず出来たらいいか!」と思ってやってきましたが、CのソースファイルやらインクルードファイルやらSUBファイルやら、 コンパイルするたびに生成されるOBJ・MAP・ABS・MOTファイルがひとつのフォルダにいっぱい出来て管理し切れない状態になりました。
 そこで止むを得ず見通しの聞くようにひとつのソースファイルごとにひとつのフォルダにまとめてそのフォルダ内にその関連のファイルを入れるようにしました。
 そのついでにバッチファイルも作ってこの作業を簡略化しようと思った次第です。
   まずそのバッチファイルを動作させるための準備です。

 先の検証プログラム(LED0)を例に挙げて説明します。

左がコンパイル用のプログラムが入っているフォルダです。
ソースファイルとSUBファイルが入っているソースファイルと同じ名前のフォルダがいくつかあります。 インクルードファイルも「include」というフォルダにすべて入れてあります。
右上が「LED0」のフォルダでコンパイル前の状態です。
ここでコンパイル用のバッチファイル「cl.bat」をDOSプロンプトから動かします。

右がフォルダ「LED0」の中の状態です。cl.batの動作前と比べてABS・MAP・MOTの3つのファイルが増えています。

一番下のMOTファイルが必要なファイルでこのファイルを3052のCPU内に送ります。

それではこのための「cl.bat」と「led0.sub」のファイルの中味を見ていきます。


まず「cl.bat」です。
動かすのはDOSプロンプトから「cl led0」と入力します。
このclのあとに入力した「led0」が「%1」に置き換わって動作します。
置き換えて考えると、「LED0¥led0.c」「led0\led0.sub」となりこのフォルダ内のファイルを使っていることがわかります。
「c38h led0\led0.abs」によってABSファイルが変換されたMOTファイルの出力先がこのフォルダ内になります。
コンパイル中の中間ファイルとしてobjファイルがカレントフォルダに作られますが、これはあとでは不要なので「del led0.obj」により最後に削除します。
echo off
d:
cd D:\denkousaku\h8\c
del err.txt
cc38h.exe -CPU=300HA -INCLUDE=d:\denkousaku\h8\c\include %1\%1.c >>err.txt
type err.txt
del errL.txt
l38h.exe -SUBCOMMAND=%1\%1.sub >> errL.txt
type errL.txt
c38h %1\%1.abs
del %1.obj
次はlid0.subです。
「OUTPUT led0\led0.bas」によりABSファイルの出力場所をled0フォルダに設定します。
「PRINT led0\led0」によてMAPファイルの出力場所をこのフォルダに設定します。
OUTPUT led0\led0.abs
PRINT led0\led0
INPUT resetv,led0
LIB c38hab
START P(200)
EXI

タイマーの使い方
 まずタイマーを検証しました。

 「なぜタイマーを始めに!」と言うのは、リアルタイムで何かしようと思うと何ミリ秒かごとに監視しながらその状態を見てどうするこうするというのを 決める必要があります。そのためにまずタイマー機能です。

 このCPUには、ITU(16ビットインテグレーティッドタイマー)が5chついています。
 タイマー動作をスタートさせると、16ビットのタイマーカウンタがカウントを開始します。GRA(ゼネラルレジスタA)に値を設定しておくとカウント値が GRAと一致したときにTSR(タイマーステータスレジスタ)IFMAというBITが1となります。このIFMAを調べることによって設定したタイマー値に なったかどうかがわかります。

このためのプログラムの概要です。

初期化関数 timerInit() を作る。
(1)TCR(タイマーコントロールレジスタ)の設定
−−−− CCLR1 CCLR0 CKEG1 CKEG0 TPSC2 TPSC1 TPSC0
TPSC2,1,0ではTCNTの基準となるクロックを選びます。011に設定すると内部クロック25MHzの1/8にできます。
CKEG1,0は00に、CCR1,0ではTCNT(タイマーカウンタ)のクリアの仕方を設定します(これをカウンタクリア要因といいます)。 GRAの値でクリアしたいですので、この場合は01にします。するとTCRすべての設定はこうなります。
−−−− CCLR1 CCLR0 CKEG1 CKEG0 TPSC2 TPSC1 TPSC0
これを16進数にすると23hです。  だから ITU0. TCR. BYTE = 0X23; となります。

(2)GRA(ゼネラルレジスタA)の設定
 TCRの設定によりTCNTの基準クロックは25/8MHzになります。
 この周期は1s÷25/8MHz=8/25μsです。 これを何倍したら1msになるかを考えます。
 1ms÷8/25μs=25/8×1000=3125 ですので3125倍するとちょうど1msになります。
だからGRA=3125に設定するとちょうど1ms後にIFMA=1となって1ms経過したことを知らせてくれます。
3125を16進数に直すと0c35hですので、  ITUO.GRA = 0x0c35;  と設定します。
 すると初期化関数timerInit()は次のようになります。
void timerInit(void)

   ITU0.TCR.BYTE = 0X23; //タイマーコントロールレジスタの設定
   ITU0.GRA = 0X0C35; //GRAを3125にして1msを1単位にする
   ITU0.TSTR.BIT.STR0 = 0; //タイマーカウント停止
}
待ち時間の関数 wait(int waitTime) を作る
 タイマーコントロールレジスタの設定によってタイマーの単位はミリ秒となりましたので、例えば500ms待つ関数の場合には wait(500)  とするような使い方をする関数を作ります。
 1msになればIFMAの値が1になるように設定してあります。そこでIFMAの値が0の間は1ms経過していないということなので 「その間何もしないで待つ」ということがキーになります。
 そのためのものは 「 while(ITU0.TSR.BIT.IFMA == 0){} 」 です。
 これによって1ms以内はじっとココで待ち続けます。1msになるとIFMA=1となりますので再度IFMA=0にします。これを設定 した waitTime の間繰り返します。
void wait(int waitTime)
{
  int i;
  ITU0.TSTR.BIT.STR0 = 1;//タイムカウント開始
  for ( i=0 ; i < waitTime; i++) {
   while(ITU0.TSR.BIT.IFMA == 0) {}//1msになるまでここで待つ
   ITU0.TSR.BIT.IFMA = 0;//1ms経過したらこの値が1になっているのでこの値を0に戻して再度1msの検知開始
  }
  ITU0.TSTR.BIT.STR0 = 0;//タイムカウント終了
}
timerInit() 、wait( ) を使ってメイン関数 main() を作る
 作成した timerInit と wait() によってmain関数は次のようになります。
 このようにすると「Aの処理」をしてから500ms経過後に「Bの処理」をするということになります。
void main()
{
  timerInit();//タイマーコントロールレジスタの初期化
  Aの処理
  wait(500)//500ms待つ
  Bの処理
先ほどの検証用回路を使って、このタイマーで1秒ごとに8個のLEDが順番に点灯していく状況を作ります。下がそのためのプログラムです。
/****************************************/
/* タイマーテスト 2009/08/14 */
/* timtest1.c */
/* H8-3052 */
/* ポート1のLEDを順番に点灯     */
/****************************************/
#include <3048f.h>
/* 待ち時間発生初期化 ************************************/
void timer_init(void){
ITU0.TCR.BYTE = 0x23; /* GRAコンペアマッチ clock 1/8 */
ITU0.GRA = 0x0c35; /* GRAを3125に設定して1msを1単位にする */
ITU.TSTR.BIT.STR0 = 0; /* カウント停止状態 */
}
/* 待ち時間発生 引数に、必要なミリ秒を指定する***********/
void wait(int waitTime){
int i;
ITU.TSTR.BIT.STR0 = 1; /* ITU0 TCNTカウント開始 */
for(i=0;i while(ITU0.TSR.BIT.IMFA == 0){} /* TCNT = GRAになるまで待つ(1mS) */
ITU0.TSR.BIT.IMFA = 0; /* 検知フラグを戻して再開 */
}
ITU.TSTR.BIT.STR0 = 0; /* ITU0 TCNTカウント終了 */
}
/* メイン関数 ********************************************/
//* Port1のLEDを順番に点灯させる */
void main(void){
unsigned char data[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
int i;
timer_init(); /* timerの初期化 */
P1.DDR = 0xff; /* port1出力に設定 表示LED */
i = 0;
while(1) {
P1.DR.BYTE = data[i];
wait(1000); /* 1000mS待つ */
i++;
if( i>7 ) i=0;
}
}

AD変換の使い方
AD変換機能の初期設定
 AD変換の設定にはADCSR(ADコントロールステータスレジスタ)を使います。
ADF ADIE ADST SCAN CKS CH2 CH1 CH0
ADF(ADEndFlag):AD変換が終了したときに1になります。
ADIE(ADInterruptEnable):AD変換の終了で割り込みを発生させるかどうかの設定です。
ADST(ADStart):AD変換の開始・停止の設定。0で停止、1で開始。
SCAN(ScanMode):AD変換をするときに複数のチャンネルを使うかどうかの設定。
CKS(ClockSelect):AD変換時間の設定。0で低速モード、1で高速モード。
CH2・CH1・CH0は単一モード、スキャンモードの設定は以下のようです。
CH2 CH1 CH0 単一モード スキャンモード
AN0 AN0
AN1 AN0,1
AN2 AN0,1,2
AN3 AN0,1,2,3 
AN4 AN4
AN5 AN4,5
AN6 AN4,5,6
AN7 AN4,5,6,7
3048のヘッダーファイル「3048F.H」でこれが簡単に扱えるように定義されています。
このレジスタ自体をCSRとして定義されており、レジスタ全体は例えば AD.CSR.BYTE=0X33、
それぞれのビット値は例えば
AD.CSR.BIT.ADF = 1;   AD.CSR.BIT.ADIE = 0;  AD.CSR.BIT.ADST = 1;AD.CSR.BIT.SCAN = 0;
CHの値は3ビットまとめて0から7までの数値で設定します。例えばスキャンモードでAN012を使う場合は AD.CSR.BIT.CH = 2; となります。
AD変換を使って見る
AD変換の検証の例題として「ボリュームをまわしていくとそれに連れて8個のLEDが順番に点灯していく」 ということを考えて見ましょう。
 AD変換のためのピンはポート7の0から7ビットです。このピンはCN2の12から19番目のピンです。 今回はひとつのボリュームだけを使うのでこの出力をポート7の0ビット目(CN2の12ピン)につなぎます。
 右がこのための回路図で、左がそれを実際に組んだものです。検証のためだけに使いますので今回はブレッドボードに組みました。
グラウンドはCPUボードのグラウンドコネクタにつなぎ、VCC(5V)はCPUボードの5Vのコネクタにつなぎます。
青い小さなものが5Kの半抵抗です。この真ん中に見えている黄色のところをプラスドライバで回していくと0K〜5Kまで 抵抗値を変化させることが出来ます。
H8(3052)のAD変換機能は10ビットの精度を持っていて、0〜5Vの電圧を0〜1023の1024段階の 数値に変換することが出来ます。
 この変換結果は16ビットレジスタの上位10ビットに収められます。
15 14 13 12 11 10
AD9 AD8 AD7 AD6 AD5 AD4 AD3 AD2 AD1 AD0 −−− −−− −−− −−− −−− −−−
 下位6ビットは使われていませんので、このままの10ビットの精度で取り出そうとすると右方向に6ビットシフトする 必要があります。
 ここではこれほどの精度は必要ありませんので256段階の精度にして取り出します。そこで右方向に8ビットシフトします。
 プログラムでは AD.DRA>>6 という風に書きます。「>>」は右方向にシフトするという意味になります。このDRA とはAD変換の結果が収められているレジスタの名前です。
   このようにして、プログラムで好みの精度にして取り出すことが出来ます。

 半抵抗の真ん中の黄色をドライバで回転させることによって抵抗値が0から5Kまで変化していきます。これによってP7−0 に送られる電圧値が0V〜5Vまで変化します。プログラムによってこの電圧値をAD変換して0〜255の数値に変換します。
 この電圧値を32で割ることにより8段階のレベルにしてそれによって該当のLEDが点灯します。
 ということですので、ドライバでまわしていくにしたがってLEDが順番に点灯していきます。

 下がそのプログラムです。
 AD.CSR.BYTE = 0X08 は単一モード、高速変換の設定です。

サーボモータの扱い方
 DCモーターについては、モータドライバIC(TA7291)を使ってコントロールすることを理解したつもりです。今度はサーボモータのコントロールに挑戦しました。
 サーボモータはロボットの関節の制御に使われるモータで、正確に回転角度の制御が出来ます。 2足歩行ロボットを最終目標としている僕としてはなんとしても理解する必要があります。
 サーボモータのコントロールにはPWM(Pulse Width Modulatoin パルス幅変調)方式を使います。このPWMはDCモータのコントロールのときにも使うことが出来ます。
 前にDCモータを使った頃は電子工作の初心者の域を出ないレベルだったでしたので極力簡単な方法で作りました。しかしサーボモータのコントロールはPWM しか出来ません。今回は腰をすえて書物やrikiyaさんの説明を読んで理解しました。
サーボモータとPWM(パルス幅変調)
 ある一定の周期で幅いくらかのパルスがあるという電気信号を考えます。
 簡単に言えば、高速で電源を切ったり入れたりするようなことです。
   このような電気信号をDCモータに与えますと、パルス幅の変化にDCモータの回転子が付いていけなくなり、その結果パルス幅に見合った 電圧がDCモータに加えられたことと同じになります。
 これによってDCモータに加える電圧の強さをパルス幅の多少によって制御することができます。
 このようにパルス幅によって、モータを制御する方式をPWM(パルス幅変調)方式といいます。
 サーボモータの場合は、加える電気信号はこのようなPWMの波形のもの、と決まっています。
 また、ある一定の周期で幅なんぼのパルスを与えるとサーボモータの角度が何度になる、と決まっています。
 「20msの周期」で「1ms〜2ms幅のパルスを与えるとサーボモータの回転子角度は0°から180°」になるようです。
PWMでサーボモータを動かす
 20msの周期で1ms、1.5ms、2ms、1.5ms、1ms・・・とパルス幅をサーボモータに与え続けますと、サーボモータの 回転子は左・真ん中・右・真ん中・左・・・・・と左右の首ふりを繰り返します。
 このためにどういう風にすればいいかを考えて検証します。
 H8(3052)では、タイマー機能のITU(インテグレーティッドタイマーユニット)にPWMを使う機能があります。
 ちょうど5チャンネル分のITU0〜4を使って、PWM信号をTIOCA0(PA2)、TIOCA1(PA4) TIOCA2(PA6)、TIOCA3(PB0)、TIOCA4(PB2)の端子から出力できます。
 今回は単に1チャンネルのみを使いますのでTIOCA0を使います。出力端子はPA2です。コネクタはCN1の10ピン目となります。
 PWMにITU0を使います。

 まずPWMとして使うITU0の設定をします。
 システムクロックは25MHzです。内部クロックは1/8の設定で使いますので周期を設定するためのクロックは25/8MHzとなります。 8/25μsごとにカウンタがアップしていきます。
 前と同じようにカウンタクリア要因(GRAの値と同じなればタイマーカウンタをクリア)としてGRAの値を使います。
 すでに説明したタイマー機能と同じ設定です。
 この設定によりGRAの値により周期が決まります。またパルス幅はGRBの値で設定します。

 周期を20msにするためのGRAの値=20ms÷8/25μs=62.5×1000=62500です。
 パルス幅2msのためのGRBの値=上の値の1/10=6250です。
 パルス幅1msのためのGRBの値=さらに1/2=3125です。
 パルス幅1.5msのためのGRB=(6250+3125)/2=約4688です。

 タイマーをPWMとして使うための設定はITU.TMDR(タイマーモードレジスタ)で行います。
 このレジスタは下のような8ビットレジスタで、5チャンネル分のPWMの設定をします。
 今はITU0のPWMをONにするので、ITU.TMDR.BIT.PWM0=1 とします。MDF・FDIRは 今回は使用しません。 
−−− MDF FDIR PWM4 PWM3 PWM2 PWM1 PWM0

サーボモータは左です。
端子から3本の線が出ています。真ん中の赤には5Vをつなぎますが、DCモータと同じように電源は外部電源を使います。
一番黒っぽい線はグラウンド、もう一本は信号線です。今の場合はPA2のピン(CN1−10)につなぎます。

右が実際につないだものです。
スイッチをONにするとサーボの回転子が左右の首ふりをします。

プログラムを下に示します。
見てもらいますとわかるように、ITU0をPWM制御に使っていますので、 時間待ちのためのタイマーにはITU1を使っています。
timer1_Init(void) 、とかwait1(int waitTime) というようにITU0を使った時間待ちの関数の 名前と区別できるようにしています。
 このプログラムは長そうですが、timer1_init とwait1は時間待ちのためにあるだけなので、 main関数のみを見てもらうとわかりやすいと思います。