前回 のリモコン側プログラムに続き、マイコン・カー本体側のプログラムを解説します。 その後、ユーザ・モード・モニタ入りではない QY4A マイコンを使う場合の手順を説明します。
ユーザ・モード・モニタ入りではない QY4A マイコンの使用例
固有のトリム値を鉛筆でメモ書きしている (クリックで拡大)
◆ マイコン・カー本体側のプログラム解説
前回 に引き続き、ユーザ・モード・モニタ入りマイコンを使ったプログラムの解説です。 マイコン・カー本体側プログラム・リストの主要部分を順次示します。
//*************************
// マクロの定義
//*************************// 全体
#define PORT_TST ( PTA_PTA5 ) // 仮のテスト出力として使う
#define PORT_MT ( PTB ) // 上位4ビットがモータ出力// SCI 受信処理(ポーリング)
#define PORT_RXD ( PTA_PTA2 ) // RxDを入力ポートとして扱う
#define BIT_TIME ( 122 )
#define BIT_TIME0_5 ( BIT_TIME / 2 )
各種のマクロ定義を行っています。 入出力ポートに名前を付け、時間調整用の数値にも名前を付けています。 BIT_TIME は 前回 の (*2) の要領で波形を見ながら決定しました。
//*************************
// データの定義
//*************************// SCI 受信処理(ポーリング)
U1 U1_rx_data;
U1 U1_rx_flag;
使用するデータについては、関数をまたいで読み書きする変数をグローバル変数にしました。 U1 という型名は符号なし 1バイトとして定義しています。
//*************************
// メイン処理
//*************************void main( void ) {
init_hardware( );
init_PORT( );
// EnableInterrupts;
for ( ; ; ) {
__RESET_WATCHDOG( );
sci_receive_pol( ); // SCI 受信処理(ポーリング)
if ( U1_rx_flag ){
PORT_MT = 0xF0 & ( U1_rx_data << 4 );
U1_rx_flag = 0;
}
}
}
メイン関数では初期化を行ったあと、メインのループに入ります。 SCI 受信処理(ポーリング)関数は、待機状態(アイドル状態)のとき RxD 入力ポートが ”H” の場合はそのまま何もせず抜け出します。 今回は特にないのですが、メイン・ループにほかの仕事がある場合はそれが動けるようにという配慮です(COPクリアを行っている)。 そしてこの関数を繰り返し呼び出すうち RxD 入力ポートが ”L” になると受信動作が開始されます。 受信動作が開始されると、受信が完了するかまたはエラーによって受信に失敗したとき関数を抜け出します。
メイン・ループで実行する仕事は次のとおりです。
(1) COP クリアを行う。
(2) SCI 受信処理(ポーリング)関数を呼び出す。
(3) U1_rx_flag が 1 のときは受信完了の意味なので、次の仕事を行う。
・ 受信データを 4ビット左シフト(データの各ビットを左隣のビットへ移動する
こと。もともと左端にあったビットの内容は消える)した内容に対してマスクを
かけ(*1)、上位 4ビットだけ有効にしたものをモータ出力ポートにコピーする。
・ U1_rx_flag を 0 にする。
これを繰り返します。 結果として、リモコンが接続されているときは約 10ms ごとに 1フレーム(1バイト)受信とポート出力が行われます。
最後に示すのは SCI 受信処理(ポーリング)関数です。 これについてはプログラム・リストを眺めるだけではちょっと理解しにくいと思います(これが最も良い書き方というわけでもないですし)。 この関数の内部では 第48回 と 第56回 で説明した 「状態遷移」 の手法を用いました。 すっかり忘れたという方は読み返してみてください。
というわけで、設計時に(頭の中で)書いた状態遷移図を示します。 参考にしてください。
SCI 受信処理(ポーリング) の状態遷移図 (クリックで拡大)
テストポートは 1ビットごとの受信内容をモニタ出力する
条件3 と 条件6 はエラー発生による終了
//***************************
// SCI 受信処理(ポーリング)
//***************************
void sci_receive_pol( void ){
static U1 U1s_rx_data_old = 0;
U1 U1t_state = 0; // 初期状態はアイドル状態
U1 U1t_data;
U1 U1t_bit_position;
volatile U1 U1t_tmr;
// 条件1 立ち下がりあり
if ( !PORT_RXD ){
U1_rx_flag = 0;
U1t_state = 1;
while ( U1t_state ){
switch ( U1t_state ){
case 1: // スタートビット確認状態
for ( U1t_tmr = 0 ; U1t_tmr < BIT_TIME0_5 ; U1t_tmr++ ){
;
}
PORT_TST = PORT_RXD;
// 条件2
if ( !PORT_RXD ){
U1t_data = 0;
U1t_bit_position = 0x01;
U1t_state++;
} else { // 条件3
U1t_state = 0;
}
break;
case 2: // データ受信状態
for ( U1t_tmr = 0 ; U1t_tmr < BIT_TIME ; U1t_tmr++ ){
;
}
PORT_TST = PORT_RXD;
// この状態の処理
if ( PORT_RXD ){
U1t_data += U1t_bit_position;
}
// 条件4
if ( U1t_bit_position >= 0x80 ){
U1t_state++;
}
U1t_bit_position <<= 1;
break;
case 3: // ストップビット確認状態
for ( U1t_tmr = 0 ; U1t_tmr < BIT_TIME ; U1t_tmr++ ){
;
}
PORT_TST = PORT_RXD;
// 条件5
if ( PORT_RXD ){
U1_rx_data = U1t_data;
U1_rx_flag = 1;
}
// 条件6(条件5との共通処理)
U1t_state = 0;
break;
default: // ガード処理
U1t_state = 0;
}
}
}
}
先ほどの状態遷移図と合わせて見れば、ほとんど無理なく理解できると思います。 我ながら 「ちょっとクセがあるかな」 と感じているのは、アイドル状態を除くすべての状態を while 文でループにしている点です。 ほかの書き方もできたはずなのですが、実は割り込みを使ったほかのプログラムのシリアル受信処理を利用して仕上げたため、このような形になりました。
念のために言葉で説明しておきます。 大雑把にいって、次のようなことを行っています。
・ アイドル状態
RxD ポートを監視して ”L” のときスタートビットの確認を開始します。
・ スタート・ビット確認状態
1ビットの半分の時間だけ待ってから RxD ポートを確認し、”L” であれば
rx_flag = 0 にして新しいフレームの受信を開始します。
・ データ・ビット受信状態
1ビット分の時間間隔ごとに RxD ポートの状態を読み込んで、最下位ビット
から順番に 8ビットの受信データを組み立てます。
・ ストップ・ビット確認状態
1ビット分の時間を待ってから RxD ポートを確認し、”H” であれば正常に
1フレームの受信が完了したとみなして rx_data を更新し、rx_flag = 1 に
して受信完了を表します。
・ スタート・ビットまたはストップ・ビットの確認に失敗したときと、受信完了時は
アイドル状態に戻して次のフレーム受信に備えます。
一見ややこしく見える処理も、このように解説されるとなんとなく理解できたと思えるのではないでしょうか。 あとは慣れですので、サンプル・プログラムを利用して自分なりのプログラムを作ってみることをお勧めします。
◆ ユーザ・モード・モニタ入りではない QY4A マイコンを使う場合
この作品において、ユーザ・モード・モニタ入りではない QY4A マイコンを使うためには大きく分けて次の二通りの方法があります。
(A) 下記に示す筆者のホームページを参考にして、自分でユーザ・モード・モニタを書き込む方法。
マイコン工作の実験室
→ ■ Freescale MC68HC908Q Family (HC08マイコン工作)
→ KMC908QY4Aユーザモードモニタ入りマイコン
ユーザモード・モニタ入りHC08マイコンの作り方
(A) の場合は手順が多少面倒くさいのですが、これでユーザ・モード・モニタ入りマイコンになりますから今までの説明の通りに作業することができるようになります。 もちろん内蔵発振器モジュールの発振周波数は適正に調整された状態になります。
(B) ユーザ・モード・モニタを使わないプログラムを用意して、書き込む方法。
(B) の場合は内蔵発振器モジュール発振周波数調整用の値を個別に調べて、プログラム・ソース上に直接記述してしまう方法です。 これも少し面倒くさいのですが、(A) に比べればずっと簡単です。 (B) の方法を行う具体的な手順を説明します。
(1) 下記に示す筆者のホームページを参考にして、最適なトリム値を求める。
マイコン工作の実験室
→ ■ Freescale MC68HC908Q Family (HC08マイコン工作)
→ 特集: HC08スターター・ボード・キット
[7] LED点滅で16進表示するオートキャリブレータ
(2) 求めたトリム値(16進数) を鉛筆でマイコンに書き込む。
(1) と (2) をマイコン 2個について実施する (一番上の写真をよく見ると本当に書いてあるのが確認できる)。 例: 0xB1 と 0x89 だったとする。
(3) あらかじめ用意したリモコン側のプログラム serial_rem_01.zip をダウンロードして展開(解凍)し、CodeWarrior で開く。 serial_rem_01u.zip とは異なるので注意。
(4) 下記の 0x80 の部分をマイコン固有の値に書き換える。 例: 0xB1 に書き換える。
//*************************
// マクロの定義
//*************************
// 全体
#define TRIM_VALUE ( 0x80 ) // ←マイコンの個体ごとに異なる値を設定する
#define PORT_TST ( PTB_PTB4 )
#define MAIN_WAIT ( 407 ) // 全体を約 10ms で繰り返す
(5) HC08スタータ・ボードを使い、マイコンを間違えないように注意してこのプログラムを書き込む。 例: マイコンには B1 と書き込んであるはず。
(6) シリアル式リモコンのマイコンを外して (5) で用意したマイコンを取り付ける(向きに注意)。
(7) マイコン・カーの電源スイッチをオンにして、シリアル式リモコンが正しく使えることを確認する。
(8) あらかじめ用意したマイコン・カー本体側のプログラム serial_01.zip をダウンロードして展開(解凍)し、CodeWarrior で開く。 serial_01u.zip とは異なるので注意。
(9) 下記の 0x80 の部分をマイコン固有の値に書き換える。 例: 0x89 に書き換える。
//*************************
// マクロの定義
//*************************// 全体
#define TRIM_VALUE ( 0x80 ) // ←マイコンの個体ごとに異なる値を設定する
#define PORT_TST ( PTA_PTA5 ) // 仮のテスト出力として使う
#define PORT_MT ( PTB ) // 上位4ビットがモータ出力
(10) HC08スタータ・ボードを使い、マイコンを間違えないように注意してこのプログラムを書き込む。 例: マイコンに 89 と書き込んであるはず。
(11) マイコン・カー本体のマイコンを外して (10) で用意したマイコンを取り付ける(向きに注意)。
(12) マイコン・カーの電源スイッチをオンにして、シリアル式リモコンおよびマイコン・カー本体が正しく使えることを確認する。(*2)
なぜこれで内蔵発振器の周波数が調整できるかというと、次のような仕掛けを入れてあるからです。
//*************************
// リセット後の初期化処理
//*************************void init_hardware( void ) {
CONFIG1 = 0x10; // LVI無効、STOP無効、COP有効
CONFIG2 = 0x00; // IRQピン禁止
if ( Optional != 0xFF ){
OSCTRIM = Optional; // 0xFFC0番地 にTrim値があるときはそれを使う
} else {
OSCTRIM = TRIM_VALUE; // ないときはソース上で指定した値を使う
}
}
Optional というのはフラッシュ・メモリ 0xFFC0 番地のことで、フリースケール純正の書き込みツールを使った場合はここに周波数調整用のトリム値が自動的に格納されます。 HC08スタータ・ボードを使った場合はそういう機能がないため、何も書き込まれず内容が 0xFF のままになります。 このことを利用して、フラッシュ・メモリ 0xFFC0番地にトリム値があるときはそれを使い、ないときはプログラム・ソース上で指定した値を使うようにしています。 発振器(OSC) については 第58回 ~ 第60回 を参考にしてください。
次回はスタック・ポインタの説明をする予定です。
(*1) マスクをかけて上位 4ビットだけ有効に、という部分はなくても支障ありません。
つまり PORT_MT = U1_rx_data << 4; でも大丈夫です。
(*2) 念のために前回の (*4) と同じ要領で波形を確認してみると、同じ結果が得られた。 下図は CH1 が TXD 信号、CH2 をマイコン・カー側のマイコンの PTA5 に接続している。 データ・ビットのパターンは 「左正転」 と 「右逆転」 を押したところ。 CH2 の信号の意味は 1ビットずつ受信した内容をモニタ出力しているので(上の状態遷移図を参照)、ちょうど入力されたシリアル信号のビットの中間あたりで正常な判定ができていることが確認できた。
本体側で 1ビットずつ確実に受信できていることを確認 (クリックで拡大)
『参考文献』
「試しながら学ぶHC08マイコン入門」 (CQ出版)
第1章 マイコン電子工作を始めよう
第10章 統合開発環境CodeWarriorを使ってみる
筆者のホームページ 『マイコン工作の実験室』
組み込みエンジニア KAWANO
