舞鶴技術研究会・技術講演会より(2004/2/28(土)) 

舞鶴技術研究会・技術講演会
参官学協同の技術力向上への取り組み事例発表会
日時:2月28日(土) 午後1時30分から
場所:ポリテクカレッジ京都(6号館2階を予定しています)舞鶴市上安1922

ものづくりプロジェクト:
「1チップ・マイコンPIC&AVRの応用技術 (応用編)」
1チップマイコンPICとRTC-ICのI2C通信によるラップカウンタの製作

舞鶴工業高等専門学校 電子制御工学科
町田秀和(machida@maizuru-ct.ac.jp)

1.はじめに
 舞鶴技術研究会の情報システム分科会では、2002年〜2003年にかけて 「ものづくりプロジェクト:1チップ・マイコンPIC&AVRの応用技術」 を立ち上げ、以下の公開講座などを実施し、会員および舞鶴高専学生の 多くの参加を得ました。 その結果、以下のような問い合わせや製作例などを寄せていただきました。  本プロジェクトは2003年度で終了しますが、1チップ・マイコンPICは応用範囲が 広い割に、気軽に取り組むことができることが、ご理解いただけてきたと実感して います。今後とも、1チップ・マイコンに関するお問い合わせがありましたら、町田 までお気軽にメール(machida@maizuru-ct.ac.jp)でご連絡ください。

 本稿では、2003年度に公開講座(応用編)のネタとして取り上げた、
写真1に示す「1チップマイコンPICとRTC-ICのI2C通信によるラップカウンタの製作」 を紹介します。
 キーポイントは以下のとおりです。

写真1 ラップ・カウンタ全景(実物大)


2.1チップマイコンPIC
 マイクロ・コンピュータは電子制御機器の中核として、例えば炊飯器、ミシン、洗濯機、 自動車、などなどに使われています。もはや必要不可欠な存在と言ってよいでしょう。しかし ながら電子制御工学科でも最近は3年生の「計算機工学1」で初めて触れられるだけで、それも 「基本情報処理技術者試験」で出題されるCASL2/COMET2という仮想のアセンブラ/マイコンを 取り扱っているだけです(今後カリキュラムは改善されていきます)。また、以前は実際にシス テムを実現するにはCPU(Z80)のほかにメモリ(S-RAM)や並列入出力LSI8255を搭載したワンボード マイコンを使用していて、とても気軽に使える物ではなく、それなりの値段がしました。
 しかし最近になってごく小規模な組み込み用途向けのコントローラ として、8〜40ピンの 1チップ・マイコンが流行しています。例えば、米国Microchip Technology社のPICや、 米国Atmel社のAVRなどです。これらの1チップマイコンは高速(20MHz)、低消費電力(最小30μA)、 十分なメモリ(数キロバイト)、強力な入出力ピン(RS232C,I2Cも可能)、最小の周辺回路、 低価格(320円程度)で、そして開発ツール(ソフトウェア:ほとんどフリー)が豊富でパソコン で簡単に開発できるところが最大の魅力です。
 ということで現実的なマイコン応用の実現手段として、大変人気があります。実際、ロボコン ではほとんどのチームがPICを用いています。また、ベンチャー企業でも盛んに用いられています。 その理由の一つとしては、PICが大変タフなことが上げられるでしょう。つまり、何度でも手軽に プログラムが可能なことや、すくそばでトランジスタが破裂してもPIC自体は無事なことなどです。

 S研では年度初めに学生どおしで新入部員対象に部内講習会を行い、また例年秋頃には筆者らが 一般市民相手に公開講座を開いています。本校学生の参加も問題ありませんのでお問い合わせて ください。

PICの開発手順は詳しくは参考文献1) 2)にありますが、簡単な流れは次の通りです。
  1. ハードウェア回路を設計制作する。ここでは写真2、図1,2の回路を用いました。
  2. 開発ツールMPLAB上で、アセンブラもしくはC言語でプログラムを制作する。
  3. 書込み器をパソコンのシリアルポートに接続し、 参考文献3)のic-progを用いて書込む。


写真2 PICのハードウェア例(実物大)

写真2の右側がライタ部分(左端のコネクタでPCのシリアルポートに接続)、左側が汎用評価 ボード部分(入力スイッチx3,リセットスイッチx1,出力LEDx2,液晶LCDユニット用コネクタ, I2Cリアリタムクロック用ソケット付)です。

図1 実体配線図(プリント基板CAD「PCBE」で作成)


図2 全回路図(参考文献4)Bschによる)

写真2の右側がライタ部分(左端のコネクタでPCのシリアルポートに接続)、左側が汎用評価 ボード部分(入力スイッチx3,リセットスイッチx1,出力LEDx2,液晶LCDユニット用コネクタ, I2Cリアリタムクロック用ソケット付)です。

 以上のうち、Cコンパイラのみは有料(25,000円程度)ですが、その他はいっさいフリー (無料)です。筆者の感想としては、アセンブリ言語はとっつきにくくはありますが、すでに 参考文献2) などに膨大なプログラム資産があるので、それを参照すれば目的はすぐに達成 できるというところです。一方、C言語プログラムはライブラリ(RS232C,I2C通信やタイマ など)がそろっているので、高級言語らしく大変楽にプログラミングができますが、なんと いっても有料であること、そしてやはりアセンブリ言語に対しては非効率なことと、 わずかに予期しない動作をすることがあります。この理由から、ロボコンでは全て アセンブリ言語で記述しているそうです。


3.RTC-IC(EPSON RTC-8564)とI2C通信
 (1チップ)マイコンで時計(ラップ・カウンタ)を実現するには、 何らかの方法で時間を計測するわけだが、それには次の3つの方法が考えられます。
  1. 命令数と実行時間をかけて時間を計る。
  2. 周辺装置(1チップマイコンでは内臓)のカウンタの割り込みで時間を計る。
  3. 専用のRTC(Real Time Clock)-ICを外付けして時間を読み書きする。
 1,2の方法は外付け回路が不要なので安価にはできますが、一番の問題は 命令実行時間(クロック周波数)の精度に依存し、また割り込みを用いると 往々にして狂ってしまうことがあります。
 これに対して、3のRTC-IC外付けの方法は専用ICだけあって正確であるし、 アクセス方法さえ判明すれば、プログラミングも難しくはありません。

 そこで、ここではEPSON社のRTC-ICであるRTC8564を採用しました。 ( 秋月電子通商で通販しています。 約500円程度)
 問題のアクセス方法ですが、2線式シリアル通信方式であるI2Cが使われています。 I2Cはピン数が少ない1チップマイコン(PIC)には大変適した方式であり、PIC16F87*シリーズ には専用ハードウェア回路が内蔵されているし、最も普及しているPより小型のPIC16F84 でもソフトウェアでインターフェースすることができます。

I2C(Inter-Integrated Circuit)とは、フィリップス社が提唱した周辺デバイスとの シリアル通信方式で、マイコン間はもちろんEEPROMやA/D変換ICそしてRTC-IC のインターフェースが実現されています。このような目的のため、同じ基板内などの ように近距離で直結したデバイスと最大400kbpsの通信に用いられるのが主で、 離れた装置間の通信には向いていません。

 Cコンパイラ(参考文献6) のCCS社のPCM)にはI2Cの基本ライブラリ関数が用意されていて大変簡単にアクセス することができます。
 さらに、付録に町田が自作したRTC8564用の通信ライブラリを示しますが、 読み書きシーケンスは図3に示すとおりです。
 気を付けないといけないのは、RTC(I2C)から読み出す場合には、最後の読み出し ではack(返答)を返さない(0)とすることです。そうしないと、RTC(I2C)が 読み出しモードから抜け出せずに固まってしまいます。  

図3 I2C通信によるRTCの読み書きシーケンス




4.液晶表示
 時間の表示には液晶を用いています。ここでは1チップマイコンPICでは デファクトスタンダードになっているサンライク社のSC1602Bを採用しました。
 SC1602Bは4bitのデータの他3本の制御信号だけで駆動できるので、ピン数の 少ないPICには適しています。付録に液晶を駆動するためのC言語ライブラリを示します。


5.ソフトウェアの制作
 ソフトウェアの製作にはCコンパイラ(参考文献6) のCCS社のPCM)を用いました。全プログラムを付録に示します。
 使い方は以下のとおりで、写真3のようにラップ計時が行えます。
  1. 電源投入あるいはPICをリセットするとRTCをリセットして計時を開始すします。
  2. 1周目に入るときに押ボタンスイッチ(RA4)を押すと、 RTCを再リセットし1周目の計時が始まる(スタート)
    液晶の上段(1行目)には経過時間が表示されます。
  3. 2周目に入るときに押ボタンスイッチ(RA4)を押すと、 1周目の記録が液晶の下段(2行目)に表示されます(ラップ)。
  4. n周目に入るたびに押ボタンスイッチ(RA4)を押すと、 n-1周目の記録が液晶の下段(2行目)に表示されます(ラップ)。
電源投入時

スタート

1周目計時

2周目計時

写真3 ラップカウンタの使い方
(RA4の押ボタンスイッチを押すだけ)
(MCLRの押ボタンスイッチはリセット)


 Cプログラミングは非常に記述しやすいですが、やはりPICの特性を良く把握しないと 思わぬ不具合に泣くことになります。たとえば、押しボタンスイッチが押されたことを 確認するには、以下のように20msec待って再確認してチャタリングを除去する必要が あるます。
    if(input(PIN_A4)==0) {
      delay_ms(20);
      if(input(PIN_A4)==0) {



6.おわりに
 ものづくりプロジェクトの応用編の課題として、ラップカウンタを製作しました。
 外付け回路として液晶とRTC-ICを採用したましたが、それをアクセスするライブラリ関数 を記述さえしてしまえば、プログラミングは大変簡単で様々な応用が考えられます。
 今後は、加速度センサや歩数計そして記録用のI2C-EEPROMを外付けして、 実用的なラップカウンタに仕上げていく予定です。


参考文献(リンク集)
  1. 町田、S研:「ものづくりプロジェクト:PIC&AVRによる1チップマイコンの応用技術」 の勉強会(2002/6/20)
  2. 後閑: 電子工作の実験室
  3. IC-Prog Prototype Programmer
  4. 岡田:水魚堂オンライン、回路図エディタBsch
  5. CCS社: PIC用C言語コンパイラ
  6. PIC,RTC通販: 秋月電子通商 (SSRで検索するとよい。1個250円)




付録:PICとRTC-ICのI2C通信SSRによるラップカウンラのCプログラム
付録1:メイン・プログラム(rctlap01.c)
///// rctcap01  RTC , lap counter/////

#include    <16f84.h>  
#fuses HS,NOWDT,NOPROTECT,PUT
#use delay(CLOCK = 20000000)
#use fast_io(A)
#use fast_io(B)

#use i2c(MASTER, SDA=PIN_A0, SCL=PIN_A1, ADDRESS=0xa0, FORCE_HW) // I2C使用宣言

#define mode        0             ////// 液晶表示ライブラリ設定
#define input_x     input_B       //ポートB使用
#define output_x    output_B
#define set_tris_x  set_tris_B
#define stb         PIN_B3
#define rs          PIN_B2

#byte port_a=5

#include  "lcd_lib.c"  // 液晶ライブラリ

#include  "rtc_lib.c"  // RTCライブラリ
#include  "rtc_lap.c"  // ラップ計算

///// メイン関数
void main()
{
  int pa;
  int lap=-1;
   
  set_tris_a(0x3F);             // A-port all input
  output_float(PIN_A1);		//SCLピン定義
  output_float(PIN_A0);		//SDAピン定義

  lcd_init();                   //LCD初期化
  lcd_cmd(0x0C);                // cursor off
  lcd_clear();

  year=0x03; month=0x90; week=0x00; day=0x12; hour=0x00; min=0x00; sec=0x00;
  rtc_date_set();
  rtc_date_read();
  rtc_lap_init();

  while(1) {	//永久ループ
    rtc_date_read();
    lcd_cmd(0x00);        //1行目の先頭へ
    lcd_cmd(0x02);
    printf(lcd_data," Now   %c%c:%c%c:%c%c",h_hour,l_hour,h_min,l_min,h_sec,l_sec);
    if(input(PIN_A4)==0) {
      delay_ms(20);
      if(input(PIN_A4)==0) {
        lap++; 
        if(lap==0) {
          year=0x03; month=0x90; week=0x00; day=0x12; hour=0x00; min=0x00; sec=0x00;
          rtc_date_set();
          rtc_date_read();
        }
        rtc_lap_calc();
        lcd_cmd(0xC0);        //2行目の先頭へ
        printf(lcd_data," Lap%2d %c%c:%c%c:%c%c",lap,hl_hour,ll_hour,hl_min,ll_min,hl_sec,ll_sec);
        do {} while(input(PIN_A4)==0);
     } 
    } 
  }
}
// LCD表示 16文字×2
// 0123456789ABCDEF
//  Now   12:34:56 
//  Lap01 00:00:12



付録2:液晶ライブラリ(lcd_lib.c)
///////////////////////////////////////////////
//  液晶表示器制御ライブラリ
//  内蔵関数は以下
//    lcd_init()    ----- 初期化
//    lcd_cmd(cmd)  ----- コマンド出力
//    lcd_data(chr) ----- 1文字表示出力
//    lcd_clear()   ----- 全消去
//////////////////////////////////////////////

//////// データ出力サブ関数
void lcd_out(int code, int flag)
{
  output_x((code & 0xF0) | (input_x() & 0x0F));
  if (flag == 0)
    output_high(rs);   //表示データの場合
  else
    output_low(rs);    //コマンドデータの場合
  delay_cycles(1);     //NOP    
  output_high(stb);    //strobe out
  delay_cycles(2);     //NOP
  output_low(stb);     //reset strobe
}
//////// 1文字表示関数
void lcd_data(int asci)
{
  lcd_out(asci, 0);    //上位4ビット出力
  lcd_out(asci<<4, 0); //下位4ビット出力
  delay_us(50);        //50μsec待ち
}
/////// コマンド出力関数
void lcd_cmd(int cmd)
{
  lcd_out(cmd, 1);     //上位4ビット出力
  lcd_out(cmd<<4, 1);  //下位4ビット出力
  delay_ms(2);         //2msec待ち
}
/////// 全消去関数
void lcd_clear()
{
  lcd_cmd(0x01);       //初期化コマンド出力
  delay_ms(15);        //15msec待ち
}
/////// 初期化関数
void lcd_init()
{
  set_tris_x(mode);    //モードセット
  delay_ms(15);
  lcd_out(0x30, 1);    //8bit mode set
  delay_ms(5);
  lcd_out(0x30, 1);    //8bit mode set
  delay_ms(1);
  lcd_out(0x30, 1);    //8bit mode set
  delay_ms(1);
  lcd_out(0x20, 1);    //4bit mode set
  delay_ms(1);
  lcd_cmd(0x2E);       //DL=0 4bit mode
  lcd_cmd(0x08);       //display off C=D=B=0
  lcd_cmd(0x0D);       //display on C=D=1 B=0
  lcd_cmd(0x06);       //entry I/D=1 S=0
  lcd_cmd(0x02);       //cursor home
}



付録3:RTCライブラリ(rtc_lib.c)
// I2C RTC EPSON RTC-8564 functions  By H.machida@MNCT-S 2003/10/9
// rtc_lib.c

// global variables

int year,month,week,day,hour,min,sec;                // 現時刻
char h_year,h_month,h_week,h_day,h_hour,h_min,h_sec; // 現時刻文字2桁め
char l_year,l_month,l_week,l_day,l_hour,l_min,l_sec; // 現時刻文字1桁め

// 【<1>日付時刻設定】
void rtc_date_set()
{
    i2c_start();
    i2c_write(0xa2); // 書き込みモード
    i2c_write(0x02); // 秒のアドレス
    i2c_write(sec);  // 秒の値 0-59
    i2c_write(min);  // 分の値 0-59
    i2c_write(hour); // 時の値 0-23
    i2c_write(day);  // 日の値 1-31
    i2c_write(week); // 曜の値 日月火水木金土 0123456 上位5bitはDon't Car
    i2c_write(month);// 月の値 (C:MSB)1-12   Cは1のとき21世紀
    i2c_write(year); // 年の値 00-99
    i2c_stop();
}

// 【<2>日付時刻読み出し】
void rtc_date_read()
{
    i2c_start();
    i2c_write(0xa2); // 書き込みモード
    i2c_write(0x02); // 秒のアドレス
    i2c_start();
    i2c_write(0xa3); // 読み込みモード
    sec=  i2c_read(1); // 秒の値
    min=  i2c_read(1); // 分の値
    hour= i2c_read(1); // 時の値
    day=  i2c_read(1); // 日の値
    week= i2c_read(1); // 曜の値
    month=i2c_read(1); // 月の値
    year= i2c_read(0); // 年の値
    i2c_stop();
    h_sec=  ((  sec>>4)&0x07)|0x30; l_sec=  (  sec&0x0f)|0x30; 
    h_min=  ((  min>>4)&0x07)|0x30; l_min=  (  min&0x0f)|0x30; 
    h_hour= (( hour>>4)&0x03)|0x30; l_hour= ( hour&0x0f)|0x30; 
    h_day=  ((  day>>4)&0x03)|0x30; l_day=  (  day&0x0f)|0x30; 
    h_week=                      0; l_week= ( week&0x0f)|0x30; 
    h_month=((month>>4)&0x01)|0x30; l_month=(month&0x0f)|0x30; 
    h_year= (( year>>4)&0x0f)|0x30; l_year= ( year&0x0f)|0x30; 
}

// 【<3>アラームの設定】毎時10分にアラーム出力する。
void rtc_alarm_set(int aweek,int aday,int ahour,int amin)
{
    i2c_start();
    i2c_write(0xa2);  // 書き込みモード
    i2c_write(0x09);  // アラーム分のアドレス
    i2c_write(amin);  // アラーム分の値 0-59 MSBはAE
    i2c_write(ahour); // アラーム時の値 0-23
    i2c_write(aday);  // アラーム日の値 1-31
    i2c_write(aweek); // アラーム曜の値 日月火水木金土 0123456 
    i2c_stop();       //                上位5bitは Don't Care
}

// 【<4>タイマ、周波数の設定】周波数1Hz,タイマ10秒設定
void rtc_timer_set(int tfreq,int tcont,int timer)
{
    i2c_start();
    i2c_write(0xa2);  // 書き込みモード
    i2c_write(0x0D);  // クロック出力周波数のアドレス
    i2c_write(tfreq); // クロック出力周波数の値
    i2c_write(tcont); // タイマ制御値
    i2c_write(timer); // タイマ値
    i2c_stop();
}



付録4:ラップ計算(rtc_lap.c)
// I2C RTC EPSON RTC-8564 functions  By H.machida@MNCT-S 2003/10/9
// rtc_lap.c

// global variables

// int year,month,week,day,hour,min,sec;                // 現時刻
// char h_year,h_month,h_week,h_day,h_hour,h_min,h_sec; // 現時刻文字2桁め
// char l_year,l_month,l_week,l_day,l_hour,l_min,l_sec; // 現時刻文字1桁め

char hb_hour,hb_min,hb_sec; // 前時刻文字2桁め
char lb_hour,lb_min,lb_sec; // 前時刻文字1桁め

char hl_hour,hl_min,hl_sec; // ラップ時間2桁め
char ll_hour,ll_min,ll_sec; // ラップ時間文字1桁め

char ht_hour,ht_min,ht_sec; // 待避用時刻文字2桁め
char lt_hour,lt_min,lt_sec; // 待避用時刻文字1桁め

//
void rtc_lap_init()
{
  hb_hour=h_hour; hb_min=h_min; hb_sec=h_sec;
  lb_hour=l_hour; lb_min=l_min; lb_sec=l_sec;
}

// 【ラップ時間=現在時刻-前時刻】
void rtc_lap_calc()
{
  ht_hour=h_hour; ht_min=h_min; ht_sec=h_sec;
  lt_hour=l_hour; lt_min=l_min; lt_sec=l_sec;
  
  if(lb_sec > l_sec) { l_sec+=10; hb_sec++; }
  ll_sec=(l_sec-lb_sec)|0x30;
  if(hb_sec > h_sec) { h_sec+=6;  lb_min++; }
  hl_sec=(h_sec-hb_sec)|0x30;
  
  if(lb_min > l_min) { l_min+=10; hb_min++; }
  ll_min=(l_min-lb_min)|0x30;
  if(hb_min > h_min) { h_min+=6;  lb_hour++; }
  hl_min=(h_min-hb_min)|0x30;
  
  if(lb_hour > l_hour) { l_hour+=10; hb_hour++; }
  ll_hour=(l_hour-lb_hour)|0x30;
  if(hb_hour > h_hour) h_hour+=3;
  hl_hour=(h_hour-hb_hour)|0x30;

  hb_hour=ht_hour; hb_min=ht_min; hb_sec=ht_sec;
  lb_hour=lt_hour; lb_min=lt_min; lb_sec=lt_sec;
}


ご意見ご感想はこちらまで!!