この実験で使用するロボットカーは,オークス電子のセンサーラボである.このロボットには直流モータが2つ搭載されていて,左右の車輪をそれぞれ独立して駆動できるようになっている.

センサーラボでは,ポート1番と3番と7番が左右のモータを制御するIC(モータドライバIC:TA8440H)にそれぞれ接続されている.つまりポート1番と3番に適切なデータを書き込めば,左右のモータの回転方向とブレーキを制御できる.(※PWM出力は次節で説明)
| ポート番号 | ビット番号 | 内容 |
|---|---|---|
| 1 | 0 | 0: 右モータ後進,1: 右モータ前進 |
| 1 | 1 | 0: ブレーキ,1: 回転 |
| 3 | 0 | 0: 左モータ前進,1: 左モータ後進 ※右と逆 |
| 3 | 1 | 0: ブレーキ,1: 回転 |
| 7 | 4 | 右モータ用PWM出力 |
| 7 | 6 | 左モータ用PWM出力 |
これらポートはすべて出力ポートのため,pd1,pd3,pd7は「出力(0xff)」に設定しておく必要がある.
ソフトウェア上の仮想世界やゲームの世界と異なり,これから操作するセンサーラボは現実世界の製品である.停止状態からいきなり最高速度でモータを回転させるような急加速を行うと,ギアなどの駆動系が破損する.モータは,徐々に回転速度をあげるように制御しなければならない.停止するときも同様に,徐々に速度を下げなければいけない.この速度の様子をグラフにすると,台形型に見えるため台形制御と呼ぶ.

一方で,一般の直流(DC)モータの回転数を制御するための方法は,PWM(Pulse Width Modulation: パルス幅変調)制御方式を利用する.つまり,実際にはPWM制御を使用してうまくモータの回転数を制御することで,結果としてロボットに台形の加減速をさせるように(台形制御)するわけである.
DCモータは電圧ONで全速力回転,OFFで停止という単純なモータである.このモータに例えば短時間でONとOFFを繰り返すように電圧を制御すると中間の速度で回転するようになる.このときの回転速度は,ONの時間とOFFの時間の比率で決まる.当然ONの時間が長いほど高速回転で,OFFの時間が長いほど低速で回転する.このONとOFを繰り返す周期に対してONである時間の割合のことをduty:デューティと呼ぶ.

M16Cではタイマー機能の動作モードのひとつに,PWM制御用の電圧信号を出力するためのモードがある.センサーラボでは,タイマA2とタイマA3に適切な値を設定するとその値によってデューティが決まり,そのPWM信号がポート7番に出力されるように回路が組まれている.このためプログラマは,タイマA2とA3の設定をするだけで,左右のモータの回転速度を制御するようになっている.モータに関するタイマ関係のレジスタは次のとおり.(※サンプルプログラムではこれに加えて,前回使用したタイマー機能も使用している.)
| レジスタ | 機能 |
|---|---|
| ta2mr | 右モータ用タイマ設定 |
| ta2 | 右モータ用タイマの時間.ここに時間を設定→デューティが決まる→回転速度が決まる |
| ta3mr | 左モータ用タイマ設定 |
| ta3 | 左モータ用タイマの時間.ここに時間を設定→デューティが決まる→回転速度が決まる |
| tabsr | タイマ開始を合図(タイマA2とタイマA3を動かさないとモータは動かず) |
| udf | カウンタのアップ/ダウンの指定 |

ta2mrとta3mrは図のように,下位3bitはPWMモードを表す「111」とし,残りは必要に応じて設定する.例えば今回のサンプルプログラムでは,次の値を設定している.
ta2mr, ta3mrの中身:
bit7 6 5 4 3 2 1 0
0 0 1 0 0 1 1 1 = 0x27
↑
↑ 8bitPWMモードを選択
カウントソースf1(分周なし)
回転速度の方は,ta2とta3に書き込む時間によってPWMのデューティ値が決定され,それによって回転速度も決定される.8bitPWMモードの場合には,ta2とta3は次のように解釈される.
ta2, ta3の中身:
上位8bit 下位8bit
00000000 00000000
↑
↑ サイクルタイム(1サイクルの時間を表す)を決めるためのパラメータ
1サイクルのうちHighである時間→回転速度を決定
このうち,通常サイクルタイムは固定して,上位8ビットの値だけを調整して速度を決定する.サンプルプログラムではサイクルタイムのためのパラメータを「CYCLE_TIME」(=63)に固定してある.(このときサイクルタイムは1ミリ秒に固定される.)
また,上位8bitの内容を,変数dutydataに格納するとして,両者を次のようにOR結合してta2用の値に変換している.
ta2 = (dutydata<<8) | CYCLE_TIME;
この式は次のように動作する.
00110000 : dutydata
00011111 : CYCLE_TIME
↓
00110000 00000000 : (dutydata << 8) ※8ビット左シフトして上位桁へ移動
00000000 00011111 : CYCLE_TIME
↓
OR結合(|) 00110000 00011111 : どちらかが1なら1.相手の8ビットが00000000なので,単純に両者を結合したものに
datydataに設定できる値は次を目安にすること.
| dutydata | Lowのデューティ | 速度 |
|---|---|---|
| 0xfe | 0% | 停止 |
| 0xd0 | 18% | 低速 |
| 0x90 | 43% | 中速 |
| 0x60 | 86% | 高速 |
この表のように,dutydataの数値が小さい程高速になる.この表の範囲を越えないように気を付けること.(※速度はデューティが100%になるまでもう少し高速にすることができるが,あまり高速にするとギアに無理がかかりすぎて壊れてしまう.)また,dutydataを0xffにすると,モータが完全に停止して電源を入れ直さない限り起動しなくなるので注意.
PWM設定でも,前回と同様にダウンカウントさせる.
udf = 0x00; //ダウンカウントに設定
そして,最後にtabsrに起動ビットを書き込むことで,タイマーA2とA3を起動させる.次にサンプルプログラムでは,5ミリ秒ごとに少しずつ速度をあげられるように(台形制御),通常の時間をはかるためにタイマーA0も利用している.サンプルプログラムこのため,設定すべき値は次の通りになる.
tabsrの中身:
bit7 6 5 4 3 2 1 0
0 0 0 0 1 1 0 1 = 0xd
↑
↑ タイマーAOを起動
タイマーA2
↑
タイマーA3
サンプルプログラムでは,次のように各タイマーのためのビットを別に用意して,OR結合させて値を作成している.慣れればこの方がわかりやすい.
tabsr = S_MOTOR_L | S_TIMER_A0
これは次のように動作する.
00001000 ( =0x08) : S_MOTOR_L ※タイマーA2(左モータ用)を表すビット 00000001 ( =0x01) : S_TIMER_A0 ※タイマーA0(時間測定用)を表すビット ↓OR結合(|) 00001001 ( = 0x9) : S_MOTOR_L | S_TIMER_A0 ※ふたつのビットをOR結合
このサンプルプログラムは左モータを最底速度からスタートし,5ミリ秒に一回の割合で速度を変更することで,徐々に速度をあげ,最高速になったらまた最低速まで速度を落とすプログラムである.(右モータは動かしていない)
前回同様に5ミリ秒の時間をはかるためにタイマA0を使用し,さらにモータを動かすためにタイマA2を使用している.両者の区別に注意
#include "sfr62a.h"
#define PORTOUT 0xff // ポート方向レジスタを出力に設定する為のデータ
#define LOWERLIMIT 0xfe // センサーLABO使用時のモータ停止時のデータ
#define UPPERLIMIT 0x70 // センサーLABO使用時のモータ高速時のデータ
#define OFF 0x00 // フラグのOFFを示すデータ
#define ON 0x01 // フラグのONを示すデータ
#define CNT_TA0 (2500-1) // タイマA0カウンタ値 fck=f1/32
#define CYCLE_TIME (63-1) // タイマA3周期カウンタ値
#define S_MOTOR_R 0x4 // 右モータ用スタートフラグ
#define S_MOTOR_L 0x8 // 左モータ用スタートフラグ
#define S_TIMER_A0 0x1 // タイマA0用スタートフラグ
#define S_TIMER_A1 0x2 // タイマA1用スタートフラグ
void main( void )
{
unsigned char dutydata; // デューティ(モータの回転速度)用の変数
unsigned int buf; // モータへのパラメータ用
/* タイマの初期化 */
udf = 0x00; // ダウンカウント設定
ta0mr = 0x80; // タイマモード クロック:1/32
ta0 = CNT_TA0; // タイマ値の初期化
ta0ic = 0x00; // 割り込みレベルの設定
/* モータの初期化 */
p3 = 0xff; // 左モータの回転方向セット
pd3 = PORTOUT; // ポート3を出力に設定
ta3mr = 0x27; // パルス幅変調モード クロック:1/1
ta3ic = 0x00; // 割り込みレベルの設定
dutydata = LOWERLIMIT; // デューティ(モータの回転速度)を最低速に
buf = (( (unsigned int)dutydata << 8 ) | CYCLE_TIME);
// タイマ値の初期化(上位8ビットが速度,下位8ビットが周期の設定)
ta3 = buf; //そのタイマ値でタイマ3(左モータ)をセット
/* 設定されたタイマとモータを起動 */
tabsr = S_MOTOR_L | S_TIMER_A0; // タイマーのカウント開始(左モータとタイマA0) ※立てたいビットをOR結合
for( ; ; )
{/* メインループ 無限に繰り返し*/
if( ir_ta0ic == 1 )
{
/* 5msに一回だけこの中に入る */
ir_ta0ic = 0; //タイマのフラグを元に戻す
dutydata--; // 速度を一段階速める
if( dutydata < UPPERLIMIT ) // モータの最高速を超えていたら
{
dutydata = LOWERLIMIT; // 最低速に戻す
}
buf = ( (unsigned int)dutydata << 8 ) | CYCLE_TIME; //新しい速度でタイマ値を再計算
ta3 = buf; //新しいタイマ値でモータを再設定
}
/* else の部分は省略,5ms経過しないときは本当は実行されるはずの部分 */
}
}