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にプルアップして、と書いてますので、まあ未接続でもいいのでしょう。
これでマイコンとパソコンがシリアル通信できるようになりました。 次回は通信プログラムについて書きます。

2014年3月22日土曜日

電子部品と、Eagleライブラリの部品の対応

とっても探しにくいEagleの部品群。 また探すのが面倒なのでメモっておきます。

ターミナルブロック


秋月のこれとか  -> con-phoenix-508.lbr  MKDSN1,5/2-5.08

抵抗


普通のカーボン抵抗とか -> resistor.lbr  R-US_0207/7

コンデンサ


パスコンとかによく使う0.1uFのやつとか -> resistor.lbr C-EU050-025X075
電解コンデンサ(ルビコンの4.7uFとか) -> resistor.lbr CPOL-EUE2-5
            (後ろの数字は、穴ピッチ、ケース径。2-5は、穴ピッチ2mm、ケース径5mm)

クリスタル


HC-49/S(秋月のこれとか) -> crystal.lbr CRYSTALHC49S

7セグメントLED


高輝度カソードコモン(秋月のこれ) -> display-hp.lbr  HD-H103

スイッチ


タクトスイッチ(秋月のこれ) -> switch-omron.lbr  10-XX

2014年3月4日火曜日

AVRの16ビットタイマで時間の計測

AVRのマイコンにはたいてい、8ビットタイマーと16ビットタイマーの2つが用意されています。
私の場合、8ビットタイマーはPWMに使って、16ビットタイマーは時間の測定に使うことが多いです。

その、16ビットタイマーで時間の測定をするメモです。

時間の測定ということは、あるタイミングでカウンタをカウントしていくわけですが、16ビットタイマーでは当然16ビット分しかカウントできません。
カウントするタイミングは、最大1024分周、つまり1024クロックごとに1カウントという設定ができますから、
最大で計れる時間は、例えばクロックが16MHZの場合、

65535 * 1024 / 16,000,000 = 4.19424(秒)

4秒程度しか計れません。これではとても役に立たない。

それではどうするかというと、タイマカウンタが65535に達して、次に桁あふれした際に、割り込みを起こすことができます。
その割り込みを起こす度に、4.19424秒を、それまでの経過時間として、どこかに足し込んでいけばいいわけです。


具体的にはこんな感じです。
以下、ATMega328Pの16ビットタイマである、タイマ1を使う前提での説明です。
//CPUのクロック周波数
#define F_CPU 16000000
int32_t msec;//ミリ秒で保存します。

ISR(TIMER1_OVF_vect)  //タイマ1割り込みの定義
{
  msec += (65536L * 1024L * 1000L) / F_CPU ;
}
これは、ミリ秒にするために1000を掛けていますが、これでは大きくなりすぎて、32ビット数値を桁あふれしてしまいます。 そこで、予めCPU周波数を1000で割った数字を用意し、これで割りましょう。
//CPUのクロック周波数を1000で割ったもの
#define F_CPU_DIV1000 16000

int32_t msec;//ミリ秒で保存します。

ISR(TIMER1_OVF_vect)  //タイマ1割り込みの定義
{
  msec += (65536L * 1024L) / F_CPU_DIV1000 ;
}
そして、あとはタイマ関係のレジスタに設定してやれば、その通り動きます。
クラスにしてみました。ATMega328P、Atmel Studio 6.0で確認済み。
Timer.h
#include <stdint.h>

class Timer {
public:
 //タイマを設定し、起動する。
 void StartTimer();

 //タイマを起動してからの経過時間をミリ秒で返す。
 int32_t GetTime();

 //タイマを終了する。
 void EndTimer();
 
 //時間を0にリセットする。
 void ResetTime();
};
Timer.cpp
#include "Timer.h"
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/common.h>

#define F_CPU 16000000L
#define F_CPU_DIV1000 16000L

volatile int32_t current_msec;


void Timer::StartTimer()
{
 current_msec = 0;

 //割り込み禁止
 cli();

 //タイマ/カウンタ1制御レジスタAの設定
 //(タイマ用ピンとして使わない)
 TCCR1A = 0x00; //タイマ関係のピンは標準ポート動作とする
 
 //タイマ/カウンタ1制御レジスタBの設定
 TCCR1B = (1<<CS12)|(1<<CS10);//1024分周でタイマON
 
 //タイマ/カウンタ1割り込みマスク レジスタ設定
 TIMSK1 = (1<<TOIE1);//タイマ1オーバーフロー割り込み許可
 
 //割り込み許可
 sei();
}

int32_t Timer::GetTime()
{
 //16ビットレジスタの読み書きの際には、テンポラリレジスタを使用する。
 //このため、割り込み禁止操作が必要。

 //ステータスレジスタを一時保存する変数
 uint8_t sreg;

 //ステータスレジスタを保存
 sreg = SREG;

 //割り込み禁止
 cli();

 //現在のタイマ値を取得
 uint16_t t = TCNT1;

 //SREGを戻す。これによって割り込み禁止状態が戻る。(SREGのIビットを戻すから)
 SREG = sreg;
 
 int32_t tw = (int32_t)t;
 
 return current_msec + (tw * 1024) / F_CPU_DIV1000;
}

void Timer::ResetTime()
{
 uint8_t sreg;
 sreg = SREG;
 cli();

 //タイマを0にする
 TCNT1 = 0;
 SREG = sreg;

 //経過時間も0にする
 current_msec = 0;
}

void Timer::EndTimer()
{
 TCCR1B = 0;//タイマoff 
}

ISR(TIMER1_OVF_vect)  //タイマ割り込み
{
 current_msec += (65536L * 1024L)/ F_CPU_DIV1000;
}
使い方は、どこかにTimerの実体を定義しておいて、
#include "Timer.h"
Timer timer;

//タイマ起動時に一回だけ実行
timer.StartTimer();

...
...

//時刻を知りたいとき
int32_t time_elapsed = timer.GetTime(); 

...
...

//時間をリセットしたいとき
timer.ResetTime();

...
...

//タイマーが不要になったとき
timer.EndTimer();

こんな感じですかね

あ、そうそう、SREGはavr/common.hに定義されていますので、avr/interrupt.hと合わせてインクルードしておきましょう。
それと、当然ですが、この方法でもミリ秒で符号付き32ビットがいっぱいになるまでしかカウントできませんので、
0x7FFFFFFF / 1000 / 3600 / 24 = 24日程度でいっぱいになります。まあ私の用途では十分です。

2012年4月1日日曜日

AVRのメモリ(SRAM)とプログラムエリア(FLASH)

AVRではメモリ(SRAM)とプログラムエリア(FLASH)が区別される。
SRAMの容量は限られている(ATMega64Aでは4KBしかない)。その上、スタックなどにも使われる。

そこでプログラム中の文字列定数などはFLASHに置きたいが、
普通にCで書くとSRAMに置かれてしまうらしい。

1.PSTR
プログラム中ではPSTRマクロを使って文字列定数を書くことでFLASHにおいてくれる

char* flash_addr = PSTR("constant string on FLASH.");

こんな感じ


2.PROGMEM

avr/pgmspace.hをインクルードし、PROGMEMを頭につけることでFLASHに置いてくれる

PROGMEM char str[] = "some string";


3.PRG_RDB

上記で読み出すときはpgm_read_byteを使用

char c = pgm_read_byte(&str[i]);

2012年1月12日木曜日

ATMega64aのワナ

最近、ATMega64AというATMEL社のマイコンを使っています。
そこで早々にハマってしまった点をご紹介します

といってもデータシートにはちゃんと書いてあるわけで、読まずに悩んでたのがバカだったりするのですが

ハマったのは、ATMEL社のISPである、AVRISP mk2を使ったときのことです。

以前使っていた、Arduinoでも使われているATMega328pなんかだと、MISOもMOSIも素直につなげばよかったので、特に問題はありませんでした。

そのときの感覚のままでISPのMISO、MOSIをATMega64AのMISO、MOSI(PB2、PB3)につないだところ、まったく通信ができません。
おっかしーなーと散々悩んだ挙句、ついに発見しましたデータシート
310ページあたりの、「27.8 SPI Serial Programming Pin Mapping」という章です。

詳しくはデータシートを読んでください。
簡単に言うと、「SPIのIOモジュールはプログラミングインターフェースには使わないよ、SPIプログラミングモードのときはMOSIはPE0、MISOはPE1につなげよ!」ってことだったのです!
ちなみにSCKはPB1でよいです。

なんとまあ・・これで半日潰れてしまいました・・・


2011年11月6日日曜日

Google Cloud SQL

Google Cloud SQLとは?

Google Cloud SQLとは、GoogleAppEngineのアプリケーションから利用できる、リレーショナルデータベースサービスです。
それは、データベースの保守管理や運用について、(Google側で)完全に管理されたサービスであり、
利用者は、煩わしいデータベースの保守管理は不要で、自身のアプリケーションにのみ注力することができます。

データベースの仕様はMySQLに準拠しています。
これにより、データ、アプリケーション、サービスを、簡単にクラウドに載せることが可能になります。
つまり、既存のデータベースをすばやくAppEngineから利用できるようになりますので、
データの可搬性やサービスのマーケットへの投入速度を高めることができます。


アプリケーションやサービスを常に動作させるために、
GoogleCloudSQLはデータを地理的に異なる複数の場所に保存し、信頼性を高めています。

このサービスは現在プレビュー版です。
まもなく、価格についての発表があるでしょう。

機能のハイライト:

・簡単に使えます
データベースの生成、管理、監視のためのリッチなGUIがあります。

・完全に管理されています
データベースは完全に管理されており、データベースのレプリケーションや、パッチの管理など、データベースの管理運用に関わる作業は不要です。

・高い信頼性
今日のサービスやアプリケーションが必要とする信頼性を実現するため、
複数のデータセンターにまたがるデータのレプリケーション機能が組み込まれており、
ひとつのデータセンターがダウンしたとしてもサービスは使い続けることができます。

・GoogleAppEngineやその他Googleサービスとの統合
GoogleAppEngineやその他Googleサービスとの統合により、
複数の製品を簡単に連携させられるようになります。
データをクラウドに配置すれば、より高いパフォーマンスを得られるでしょう。