2014年12月15日月曜日

マイコン制御システムのユーザーインターフェイス(2) USART割り込みによる受信

マイコン-UI間通信仕様

前回、マイコンにFT232RLというUSB-シリアル変換ICをつなぎ、
UI(パソコン)とマイコン制御システムの間を物理的に接続しました。
さあ次はプログラム、といきたいところですが、その前にシステム全体の観点から、マイコンとUIの通信仕様を考察したいと思います。

よく言われるように、モジュール間は疎結合であるべきです。
今回、制御システムはUIモジュールと、なにかの制御モジュールを備えているわけで、
UIモジュールと制御モジュールはなるべく相互に依存しない作りにするべきです。
特に、制御モジュールがリアルタイムに動かなければならないのに対し、
UIモジュールは人間が相手ですので、リアルタイム性はそれほど必要ではありません。
例えば、センサが取得した値は、制御モジュールが制御に利用するのですが、
それをリアルタイムで(MPUが動く、数MHzという時間単位で)UIに表示するというのはあまり意味がありませんし、
人間からの入力を待ち受けている間、制御モジュールがストップするようなことがあってはいけません。

そこで今回は、
  1. 制御モジュールは、リアルタイムで制御を実行する。
  2. UIモジュールは、表示する値が必要なときや、人間からのなんらかの入力があった場合、制御モジュールにコマンドを送信する。
  3. 制御モジュールは、制御のリアルタイム性に影響を与えないように、UIモジュールからのコマンドを受け付け、またそのコマンドを適切に処理する。
このようなルールでUIモジュールと制御モジュールがやり取りすることとします。

具体的なコマンドの内容は対象となる制御内容によって異なってきます。
コマンドを設計する際、コマンドはアトミックにします。つまりコマンドは1つで完結し、1つのコマンドがそれに続く他のコマンドを期待するものであってはいけません。
通信は常に失敗すると考えるべきです。複数に分割されたコマンドは、制御モジュールを矛盾した状態にしてしまう可能性があります。
コマンドは、制御モジュールをクラスと考えた場合のパグリックメソッドに相当するものです。

以下は、昔作成した、電気炉のPID制御温度コントローラのコマンドの一部です。

コマンド コマンドバイト数 返信 返信バイト数 説明
Q1 2 温度 2 現在の温度を問い合わせる。バイナリ16ビットで温度が返る。
R 1 "OK" or "NG" 2 電気炉の加熱をスタートする。スタートできれば"OK"が返る。
すでに加熱中であった場合は"NG"が返る。
P 5 "OK" or "NG" 2 PID制御のゲインを設定する。ゲインは32ビット数値で指定する。コマンド"P"の後に、バイナリで4バイト分のデータをリトルエンディアン方式で送る。異常がなければ"OK"が返る。値が設定可能な範囲を超えていれば"NG"を返す。

基本的に、制御システムのコマンドというのは、固定長で設計できると思います。
可変長は終端文字の検出などいろいろ煩雑なので私はやりません。

では、次は、AVRマイコン(ここではATMega328P)のシリアル通信機能を見て行きましょう。

AVRのUSART機能

以下は、AVRのUSART機能を使用した、簡単なシリアル通信プログラムです。
//UART0初期化処理
void usart0_init(void)
{
 //USART 0 制御/状態レジスタ A の設定
 UCSR0A = 0x00;//倍速不使用、複数プロッサ動作不使用

 //USART 0 制御/状態レジスタ C の設定
 //非同期動作、パリティなし 、ストップビット1
 //データ長を8ビット(UCSZ02ビットは、UCSR0Bにある)
 UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);

 //USARTボーレートレジスタの設定
 //9600bpsとすると、
 //0x33 - CPUが8MHzのとき
 //0x67 - CPUが16MHzのとき
 UBRR0L = 0x33;
 UBRR0H = 0x00;

 //USART 0 制御/状態レジスタ Bの設定
 UCSR0B = (1<<RXEN0)|(1<<TXEN0);//受信ON、送信ON
}

//1バイトデータ送信
void usart0_Tx(const uint8_t tx_data)
{
 while( !(UCSR0A & (1<<UDRE0)) );
 UDR0 = tx_data;
}

//1バイトデータ受信
uint8_t usart0_Rx(void)
{
 while( !(UCSR0A & (1<<RXC0)) );
 return UDR0;
}

使い方は、usart0_initで初期化し、usart0_Txでデータを送信し、またusart0_Rxで受信データを待ち受けます。
さて、実際にUIとこの通信プログラムで通信しようとすると、困ってしまいます。 それは受信プログラムの以下の箇所
 while( !(UCSR0A & (1<<RXC0)) );
while・・・そうです、この受信プログラムはUCSR0AのRXC0ビットが立つまで、ずっと待っています。 UCSR0AのRXC0ビットは受信データがある場合に立つ受信フラグです。 相手が何か送ってくるまで待つならこれでいいのですが、制御システムはそうはいきません。

こういったときに便利なのが割り込みです。
USARTでは、データ受信時に割り込みを起こすことができます。
割り込みが起こった際には、即座に受信データをバッファに退避させます。
次のデータ受信に備えるために、受信割り込みルーチンは、できるだけ短い時間で終了しなければなりません。さもなくばデータを取りこぼすでしょう。
まあフロー制御をした方がいいのかもしれませんが、その辺は通信速度とMPU速度の兼ね合いですね。

割り込みを使ったUSART受信プログラム

割り込みで受信するのは非常に簡単です。 割り込みルーチンを定義して、内部レジスタで受信割り込みを許可するだけです。 以下のようになります。
void usart0_init(void)
{
 //USART 0 制御/状態レジスタ A の設定
 UCSR0A = 0x00;//倍速不使用、複数プロッサ動作不使用

 //USART 0 制御/状態レジスタ C の設定
 //非同期動作、パリティなし 、ストップビット1
 //データ長を8ビット(UCSZ02ビットは、UCSR0Bにある)
 UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);

 //USARTボーレートレジスタの設定
 //9600bpsとすると、
 //0x33 - CPUが8MHzのとき
 //0x67 - CPUが16MHzのとき
 UBRR0L = 0x33;
 UBRR0H = 0x00;

 //USART 0 制御/状態レジスタ Bの設定
 UCSR0B = (1<<RXEN0)|(1<<TXEN0)|(1<<RXCIE0);//受信ON、送信ON、受信割り込みOK
}
//受信バッファのサイズ。コマンドやシステムなどから、通常時に溢れないように決める
#define RX_BUFFER_SIZE 128
volatile uint8_t rx_buffer[RX_BUFFER_SIZE]; //受信バッファ
volatile uint8_t rx_buffer_wrote_bytes;  //受信バッファに書き込んだバイト数

ISR(USART_RX_vect)  //USART 受信割り込みベクタの定義
{
 uint8_t r = UDR0; //受信データの取得

 //受信データをバッファに保存。ただし、バッファがあふれた場合は無視する。
 if( rx_buffer_wrote_bytes < RX_BUFFER_SIZE)
 {
  rx_buffer[ rx_buffer_wrote_bytes ] = r;
  rx_buffer_wrote++;
 }
}
上記にはありませんが、もちろん、受信バッファ内のデータをコマンドとして解析し、 解析後にバッファから受信データを削除するルーチンが必要です。 また、処理が追いつかずにバッファがいっぱいになった場合は以降のデータを無視していますが、 これも他の処理が必要になる場合があるかもしれません。
そこら辺の、コマンド解析部などはまた次回ということで・・

2014年12月11日木曜日

マイコン制御システムのユーザーインターフェイス(1)

マイコンで何らかの制御システムを作るとき、厄介なのがユーザーインターフェース(UI)です。

UIは制御の本質とはあまり関係がなく、その制御システムを利用する人間に対して情報を提供し、またその人間からの要求を受け付けるためのものです。
正直なところ、制御システムを作る人間からすると興味の順位は低いのですが、
制御システムを利用する人間にとっては、そのシステムの評価自体を左右してしまう重要なものです。

7セグメントLEDや液晶モジュールを使って測定値を表示したり、ボリュームや各種スイッチを使って入力を受け付けるなど、いろいろな方法が考えられますが、
いちいちハードウェアから作ってられないよ、という場合もあります。

こんなとき、パソコンをUIとして使うと、非常に便利です。
パソコンには、グラフィカルなUI(GUI)を構築するためのツールや開発環境が豊富にあるからです。
ここでは、パソコンをマイコン制御システムのUIにする一つの方法を紹介します。

ハードウェア(FT232RLを使ったUSBバスパワー駆動マイコン)


パソコンをマイコンのUIにするのですから、当然マイコンとパソコンが物理的に繋がっていなければなりません。 そこで、パソコン側のインターフェイスは、USBを使います。 するとマイコン側にUSBと通信する仕組みが必要なのですが、それにはFTDI社のFT232RLというUSB-シリアル変換ICを使います。 パソコンとマイコンの間にこのICを使うことで、パソコンとマイコンがシリアル通信できるようになります。
つまり、パソコンのGUIでわかりやすいUIを作り、シリアル通信(マイコンのUART機能)を使って、GUIとマイコンのやり取りをしようというものです。
まず、USBには、4本のピンがあります。V+、GND、D+、D-です。
名前でだいたい想像がつきますね。
V+は5V電源のプラスです。GNDはマイナス。
D+、D-はデータ入出力のピンです。

これらのピンをFT232RLにつなぎます。せっかくですから電源はUSBからとっちゃいましょう。
ちなみに、FT232RLは3.3Vの電源も出力できます。
マイコンの電源もUSBからとれば楽ちんです。
ただし注意点がFT232RLのデータシートに書いてありました。これはUSBの規格のようです。
  1. USBにデバイスを接続すると、ホストとの間でネゴシエーションというプロセスが行われます。このとき、USBからは100mA以上とったらダメ
  2. サスペンドモードのときは2.5mA以上とったらダメ
  3. ネゴシエーション完了後、デバイス動作時は500mA以上とったらダメ

ピンの接続ですが、USBのV+は、FT232RLのVCCとVCCIOにつなぎましょう。
このとき、間にインダクタ(フェライトビーズ)を入れます。また、V+とGNDの間には10nFのコンデンサを入れ、VCCとGNDの間に100nFのコンデンサと4.7uFの電解コンデンサを入れます。        
また、3.3V出力とGNDとの間には100nFのコンデンサを入れます。
そして、FT232RLのTXDをマイコンのRXピンにつなぎ、RXDをTXピンにつなぎます。

以上を回路図にすると、以下のようになります。
ここでは、マイコンとして、ATMega328Pを使用しています。
以下、ATMega328PのUART機能を使うものとして説明していきます。


RESET#が浮いてるのが気になりますが、データシートには何も繋がないか、VCCにプルアップして、と書いてますので、まあ未接続でもいいのでしょう。
これでマイコンとパソコンがシリアル通信できるようになりました。 次回は通信プログラムについて書きます。