H8/3052マイコン実習:簡単プログラム実習

[| ]  最終更新: 2011/02/10 20:13:12

概 要

ここでは実際にプログラムをつくってみて、マイコンを動かしてみます。

マイコンでプログラムをつくって動かしてみる場合の操作は

  1. プログラムの仕様に沿ったソースプログラムを制作する。
    C言語orアセンブリ言語、テキストファイル
  2. コンパイラでコンパイルして、CPUが理解できるバイナリコードを生成する。
    コンパイル、リンク、形式変換
  3. バイナリコードをマイコンに転送する。
となります。

最後のマイコンにプログラムを仕込む段階は、対象によって様々ですが、今回使用するH8/3052はプログラム書き込みモードにして電源を投入すると、シリアルポート(SCI)からプログラムをダウンロードできるようになっています。 そのため、一度製品に組み込んでしまっても、ソフトウエアのアップデートなどは容易です。 実際に、ロボットの開発などではプログラムを修正しては書き込む、という作業を何度も行います。
(より簡単にするには、RAMを外付けして、そこにプログラムを転送して試す、という手もありますが、ワンチップマイコンらしからぬ状態になります)。
一応、書き込み寿命は100回以上などと記載されていますが、経験上派手に使っても書き込めなくなるまで使った例はほとんどありません。
(その前に電気的にどこかが壊れるため...というのも芳しくありませんが)

実習では

を行います。

プログラムのコンパイルと書き込み

準 備

まず、CDより、h8progフォルダをハードディスクの適当なところにコピーします。 後のことを考えると、ひとまず "D:\h8prog"などにすると無難です(デスクトップの上はトラブルを招くことがあります)。

h8progにはコンパイルに必要なツールと、サンプルソースがあります。

なお、不要になった場合はフォルダごと廃棄してください。

プログラムのソースコードの用意

まず、プログラムをつくります。 プログラムはC言語で、メモ帳などでテキストファイルとして作成し、保存時に拡張子を ".c"にします。

とはいえ、まっさらの状態から作ることは大変ですので、まずは以下のサンプルプログラムの中身をのぞいてみたり、それを改造するところから始めます。

プログラムのコンパイル

ソースコードをコンパイル(およびリンク)して、実際にCPU上で動作するプログラムコードをつくります。

まず、コマンドプロンプトを開いてください。
(メニュー:プログラム→アクセサリより、もしくはファイル名を指定して実行で "cmd")
そこで、先ほど用意した h8progにカレントディレクトリを移動します。 たとえば、"D:\h8prog"にいれた場合は"d:" "cd \h8prog" とします。
フォルダ名が長い場合は、フォルダを開いた状態で上部に表示されているパスをコマンドプロンプトにドラッグ&ドロップすれば手間が省けます。

そこで、プログラム("source.c"とします)をコンパイルします。

  D:\h8prog> compile source     compile以降を入力+enter
このとき、メッセージが大量に表示されますが、"error"っぽいものが混じっていなければコンパイルはできています。 念のため、compileの最終生成物である、"source.mot"があるか、その日付が最新になっているか確認してください。

H8への書き込み

最初に、マイコンボードを用意したときの H8WriteTurboを使用します。 条件をあわせておき、H8trainで書込スイッチを内側に倒し(黄色LED点灯状態)、電源を入れ直してから、H8WriteTurboにmotファイルをドロップします。 特に問題がなければ、数秒〜十数秒程度で転送が完了します。

あとは、書込スイッチを外側に倒して電源をいればおせば、書き込んだプログラムがスタートします。


パソコンにデータを送る

基本となるプログラム

いままでは、モニタプログラムとhtermに頼っていましたが、自前でパソコンにデータを送信してみます。

ポート状態を表示するプログラム (2004/03/30, 2,519 bytes)

/*   H8/3052F Test Program   */
#include "3048f.h"

/* ================================================================== *
             $BDL?.ItJ,(B
 * ================================================================== */
/* SCI$B$r=i4|2=(B */
void InitializeSCI(void)
{
  unsigned int i;
  /* SCI Initialize */
  SCI1.SCR.BYTE = 0;
  SCI1.SMR.BYTE = 0;
  SCI1.BRR = 13;
  /* BRR: 8:115200bps  13:57600  19:38400 */
  for(i=0;i<1000;i++)
    ;
  SCI1.SCR.BYTE = 0x30;   /* T,R enable */
  SCI1.SSR.BYTE &= 0x80;
}

/* $B0lJ8;zAw?.(B */
void PutChar(char c)
{
  while(!SCI1.SSR.BIT.TDRE)
    ;
  SCI1.TDR = c;
  SCI1.SSR.BIT.TDRE = 0;
}

void PrintString(char *s)
{
  while(*s)
    {
      PutChar(*s);
      s++;
    }
}

void PrintUInt(unsigned int i)
{
  int f=0;
  if((i>=10000)||f)
    { PutChar((i/10000)+'0'); f=1; i=i%10000; } else { PutChar(' '); }
  if((i>=1000 )||f)
    { PutChar((i/1000 )+'0'); f=1; i=i%1000 ; } else { PutChar(' '); }
  if((i>=100  )||f)
    { PutChar((i/100  )+'0'); f=1; i=i%100  ; } else { PutChar(' '); }
  if((i>=10   )||f)
    { PutChar((i/10   )+'0'); f=1; i=i%10   ; } else { PutChar(' '); }
  PutChar(i+'0');
}

char *_HexStr="0123456789ABCDEF";
void PrintXInt(unsigned int i)
{
  PutChar(_HexStr[(i>>12)&0xf]);
  PutChar(_HexStr[(i>> 8)&0xf]);
  PutChar(_HexStr[(i>> 4)&0xf]);
  PutChar(_HexStr[ i     &0xf]);
}

int __dummyint=0;
int __dummygint;
/* ================================================================== *
             $BDL?.ItJ,$3$3$^$G(B
 * ================================================================== */


/* $B%W%m%0%i%`K\BN(B */
void main(void)
{
  unsigned char b;
  unsigned int w,c;
  /* SCI $B=i4|2=(B */
  InitializeSCI();

  /* AD$B=i4|2=(B */
  AD.CSR.BYTE=0x33;  /* CH0-3$BO"B3JQ49(B */
  /* $BF~NOCM!'(BAD.DRA AD.DRB AD.DRC (AD.DRD>>6) 0-1023 */

  /* DA$B=i4|2=(B */
  DA.CR.BYTE=0xe0;        /* DA0,1 $BM-8z(B */
  /* $B=PNOCM!'(BDA.DR0 DA.DR1 0-255*/

  
  c=0;
  while(1)
    {
      PrintUInt(c);
      b=P1.DR.BYTE;
      PrintString("  P1:");
      PrintUInt(b);
      PrintString(" (0x"); PrintXInt(b); PrintString(") ");

      PrintString("AD:");
      w=AD.DRA>>6; /* 0-1023 */
      PrintUInt(w);
      PrintString(",");
      w=AD.DRB>>6; /* 0-1023 */
      PrintUInt(w);
      PrintString(",");
      w=AD.DRC>>6; /* 0-1023 */
      PrintUInt(w);
      PrintString(",");
      w=AD.DRD>>6; /* 0-1023 */
      PrintUInt(w);
      PrintString("\r\n");
      c++;
    }
}

C言語の詳細には触れませんが、最低限の説明をします(なんとなく、プログラムの意味は伝わると思います)。 C言語のプログラムは「関数」の集合体で構成されます。
関数名(引数)
  {
     ....
  }
という部分です。下の方にある main() という関数が、電源投入の直後に実行されます。 ここから、他の関数を呼び出す形で処理が進みます。 ちなみに、プログラム内に使われない関数があってもかまいません。

このプログラムの上半分はパソコンにSCI→シリアルポートでデータを送信するための関数群です。

main()関数の中では、まず、各種初期化を行います。
InitializeSCIを呼んで通信を初期化し、ADは前にやったように0〜3チャネルで連続変換に設定し、DAを有効化しています。
そのあとが処理の本体です。"while(1) { .. }"はずっとこの間の処理を繰り返す、という意味です。 ここでは、一周ごとに1ずつ増える変数c、ポート1の読み取り値を10進および16進数で、AD変換の値4つを10進数で、表示するようになっています。
(AD.DRA>>6)という記述がありますが、これはADの変換値は10ビットなのに、結果は16ビットレジスタの上に詰めて格納されるため、6ビット下にずらす、という操作です(/64に相当)。これで、変換値が0〜1023で得られます。 最後の"\r\n"は改行です。また、/* */ で囲まれた部分はコンパイラが無視します。

これのプログラムを早速動作させてみます。

電源をいれれば動くのですが、それでは結果を見ることができません。 適当な通信ターミナルソフト(TeraTermなど)があれば、それを利用して、なければ、h8prog内にある "sermon.exe"をつかって(設定は別途説明)出力をみてみましょう。

その上で、アナログ入力端子に可変抵抗器や、センサ類をつないでみて、値が変化していく様子を確認してください。

なお、本来のC言語にはP1とかAD.DRAといった単語はありません。一番最初で"include(含む)"されている3048f.h内で、3052の機能を使いやすくみせているものです(3048は3052の古い版でこの辺りは互換性あり)。

ITUから加速度センサを読むように改造

上のプログラムをちょっと改造して、加速度センサの出力をITUで読むようにしてみます。 加速度センサの状態を表示するプログラム (2004/03/30, 2,588 bytes)

/*   H8/3052F Test Program   */
#include "3048f.h"

/* ================================================================== *
             $BDL?.ItJ,(B
 * ================================================================== */
   $BN,(B
/* ================================================================== *
             $BDL?.ItJ,$3$3$^$G(B
 * ================================================================== */


/* $B%W%m%0%i%`K\BN(B */
void main(void)
{
  unsigned char b;
  unsigned int w,c;
  /* SCI $B=i4|2=(B */
  InitializeSCI();

  /* AD$B=i4|2=(B */
  AD.CSR.BYTE=0x33;  /* CH0-3$BO"B3JQ49(B */
  /* $BF~NOCM!'(BAD.DRA AD.DRB AD.DRC (AD.DRD>>6) 0-1023 */

  /* DA$B=i4|2=(B */
  DA.CR.BYTE=0xe0;        /* DA0,1 $BM-8z(B */
  /* $B=PNOCM!'(BDA.DR0 DA.DR1 0-255*/

  /* ITU$B=i4|2=(B ITU3,4 $B%Q%k%9I}B,Dj(B */
  ITU3.TIOR.BYTE=0x54;
  ITU3.TCR.BYTE=0x20;
  ITU4.TIOR.BYTE=0x54;
  ITU4.TCR.BYTE=0x20;
  ITU.TSTR.BYTE=0x18;
  
  c=0;
  while(1)
    {
      PrintUInt(c);
      b=P1.DR.BYTE;
      PrintString("  P1:");
      PrintUInt(b);
      PrintString(" (0x"); PrintXInt(b); PrintString(") ");

      PrintString("ITU: 3A ");
      PrintUInt(ITU3.GRA);
      PrintString(", 3B ");
      PrintUInt(ITU3.GRB);
      PrintString(", 4A ");
      PrintUInt(ITU4.GRA);
      PrintString(", 4B ");
      PrintUInt(ITU4.GRB);
      PrintString("\r\n");
      c++;
    }
}

(通信部分は同じなので略)

main()関数の中で、変更された部分は、ITU3,4の初期化が追加されたことと、while(1)の中身がITU3,4のGRA,GRBを参照するように書き換えられたことです。

これもコンパイルして書き込んで、加速度計をつないで実行してみると、角度に応じて値が変化することが確認できます。


ステッピングモータをまわす

スピードを制御する

ステッピングモータの回転速度を制御してみます。
ポート1にはプルダウン型の端子基板、ポート2にステッピングモータのドライブ回路を接続します。 ポート1の入力数値に比例した速度で回してみます。

ステッピングモータを回転させるプログラム (2004/03/30, 3,361 bytes)

/*   H8/3052F Test Program   */
#include "3048f.h"

/* ================================================================== *
             $BDL?.ItJ,(B
 * ================================================================== */
$BN,(B:$B$3$N%W%m%0%i%`$K$OITMW$G$9$1$I!"2~B$$KLrN)$D$+$bCN$l$J$$$N$G;D$C$F$^$9!#(B
/* ================================================================== *
             $BDL?.ItJ,$3$3$^$G(B
 * ================================================================== */

/* ================================================================== *
             $B%9%F%C%T%s%0%b!<%?It(B
 * ================================================================== */

#define STEPC 4
unsigned char StepPattern[STEPC]=
{
  0x01,
  0x02,
  0x04,
  0x08,
};

int stepseq;  /* $B%9%F%C%W0LCV(B */

unsigned char GetStep(int dir)
{
  /* $B%9%F%C%W0LCVJQ99(B */
  if(dir>0) stepseq++;
  if(dir<0) stepseq--;
  /* $BN>C<@\B3(B */
  if(stepseq<0) stepseq=STEPC-1;
  if(stepseq>=STEPC) stepseq=0;
  return StepPattern[stepseq];
}


/* $B%W%m%0%i%`K\BN(B */
void main(void)
{
  int speed;
  int count;
  int i;
  unsigned char b;
  /* SCI $B=i4|2=(B */
  InitializeSCI();

  /* AD$B=i4|2=(B */
  AD.CSR.BYTE=0x33;  /* CH0-3$BO"B3JQ49(B */
  /* $BF~NOCM!'(BAD.DRA AD.DRB AD.DRC (AD.DRD>>6) 0-1023 */

  /* DA$B=i4|2=(B */
  DA.CR.BYTE=0xe0;        /* DA0,1 $BM-8z(B */
  /* $B=PNOCM!'(BDA.DR0 DA.DR1 0-255*/

  /* ITU$B=i4|2=(B ITU3,4 $B%Q%k%9I}B,Dj(B */
  ITU3.TIOR.BYTE=0x54;
  ITU3.TCR.BYTE=0x20;
  ITU4.TIOR.BYTE=0x54;
  ITU4.TCR.BYTE=0x20;
  ITU.TSTR.BYTE=0x18;

  /* $B%9%F%C%T%s%0%b!<%?$O(B P2 $B$K@\B3(B */
  P2.DDR=0x0f;
  
  count=0;
  while(1)
    {
      speed=P1.DR.BYTE;
      /* $B@)8B(B */
      if(speed>10000) speed=10000;
      if(speed<-10000) speed=-10000;
      /* DDS$B=hM}(B */
      if(count+speed>10000)  /* $B!\$K$"$U$l(B */
        {
          b=GetStep(+1);
          count=count+speed-10000;
        }
      else if(count+speed<-10000) /* $B!]$K$"$U$l(B */
        {
          b=GetStep(-1);
          count=count+speed+10000;
        }
      else
        {
          b=GetStep(0);
          count=count+speed;
        }
      P2.DR.BYTE=b;
      for(i=0;i<100;i++)  /* $B;~4V$D$V$7(B */
        ;
    }
}

まず、GetStep(方向)関数は、方向が正の場合はステップを1つ進めるのに必要な励磁パターンを、負の場合は逆に一ステップ進めるためのパターンを返します。0を渡された場合は、現状維持です。
この関数では、その上で用意した StepPatternを順番に読むことで動作します。 StepPatternを読む位置はstepseq変数で、これが増減します。
この例では4パターンを順に使用するため、stepseqが3から4になったときには0に、0から−1になったときは3に強制的に変更しています(STEPCは4と等しいと定義してあります)。これによって、0-1-2-3-0-1-2-3、もしくは 3-2-1-0-3-2-1-0のような順にStepPatternを読み取ります。
(C言語ではデータのならびは0から順に数える)

main()関数では、ポート2の下4ビットをモータへの指令のために出力に切り替えて、while(1)のなかでパターン出力を行います。
さて、このプログラムでは、countとspeedという二つの変数が重要な役割を持ちます。 speedは文字通りスピードです。count変数は、while(1)が一回回る度に、speedだけ増えます。 たとえばspeed=10の場合、10ずつ増えていき、1000周に一回は、count+speed>10000の条件に反応し、GetStepに1が渡され、ステッピングモータのステップが一つ進みます。 このとき、10000を越えたぶんは、10000を引くことで、値が戻って、次の1000回で徐 々にまた増えてきます。そのあいだはGetStep(0)でパターンは進みません。
ここで、speedが20になると、500回で10000まで増えます。while(1)の中は毎回ほぼ同じ時間で処理されるので、speed=10の時に比べて、半時間でステップが進むようになります。 つまり、回転速度が倍になります。 speed=30になったら、割りきれませんが、おおむね3倍の速度になります。 このようなプログラムを組むことで、指令した速度で回せるようになります。
GetStepで得たパターンは、毎回P2.DRを通じて出力します。
(.BYTEという表記は、P2はビット単位でも操作できるため、バイト単位であることを明記)

最後の /*時間潰し*/、と記載された部分は0から99まで無駄に数えることで時間潰しをしています。 これがないと、一番遅くしても早すぎるので、適当に遅くなるように調整しました。

このプログラムは通信を行わないので、パソコン側ではなにもいりません。 プルダウン型の端子基板を取りつけれあれば、電源を入れてプログラムが動作しても動きません(speed=0になるため)。
そこで、+5V端子と、ビット0〜7の端子を適宜つないでみます(+5Vにつないだ線で端子に触ってみる、程度)。 ビット0に比較してビット1、2となるにつれ、2倍、4倍と速度があがります(いいですよね?)。

あるところまでスピードを上げると、「ぶー」と音はするものの、回り方が変になります。 これはステッピングモータの脱調と呼ばれる現象で、パルスを切り替える間に次の位置まで回転が進まなくなっておいて行かれる現象です。 こうなると、ステッピングモータの利点である、パルスを送るだけで正確に回転するという利点が無くなるため(それどころか止まったことも検出できない)、こうならない範囲で使用する必要があります。

このプログラムを改造してみましょう。

プログラムの修正を試すとき、コピーを作って名前を変えておいた方がいいでしょう。

角度を制御する

ステッピングモータの回転速度を制御してみます。
ステッピングモータを目標の角度まで回転させるプログラム (2004/03/30, 3,232 bytes)

/*   H8/3052F Test Program   */
#include "3048f.h"

/* ================================================================== *
             $BDL?.ItJ,(B
 * ================================================================== */
/* ================================================================== *
             $BDL?.ItJ,$3$3$^$G(B
 * ================================================================== */

/* ================================================================== *
             $B%9%F%C%T%s%0%b!<%?It(B
 * ================================================================== */

   $B$3$3$bF1$8(B

/* $B%W%m%0%i%`K\BN(B */
void main(void)
{
  int target;
  int current;
  int i,j;
  unsigned char b;
  /* SCI $B=i4|2=(B */
$B$3$3$bN,(B
  /* $B%9%F%C%T%s%0%b!<%?$O(B P2 $B$K@\B3(B */
  P2.DDR=0x0f;
  
  current=0;
  while(1)
    {
      target=P1.DR.BYTE;
      
      if(target<current)  /* $BL\I8$h$jBg$-$$(B */
        {
          b=GetStep(-1);
          current--;
        }
      else if(target>current)  /* $BL\I8$h$j>.$5$$(B */
        {
          b=GetStep(+1);
          current++;
        }
      else  /* $BL\I8$K0lCW(B */
        b=GetStep(0);
      P2.DR.BYTE=b;
      for(j=0;j<6;j++)
        for(i=0;i<10000;i++)  /* $B;~4V$D$V$7(B */
          ;
    }
}

今回は、targetとcurrentという変数がでてきます。 currentはモータの現在の回転角を保持していて、targetは目標角度です。 プログラム開始時を角度0としています。 targetは先ほどのspeed同様、なんらかの入力をつかいます。

ステッピングモータは前述のように、ある程度の速度以下で回す必要があります。 そのことを念頭に、ここでは、目標と現在値を比較して、それに応じてGetStep()を呼び出しています。それと同時に、現在値を更新しています。 こうすることで、1ステップずつ目標に近づけることができます。 モータの回転速度よりも早く目標が変化した場合は、一定の速度で追いかけることになります。

試し方は先ほどと同じで、端子に適当に5Vを与えてください。

これも改造してみましょう。


さらにこの先

ここまで行った操作は、序の口に過ぎません。 より複雑なシステムのためには、複雑なプログラムが必要ですし、そもそも思ったような動作をさせるためにはC言語の習得が必須です。 また、今回の通信プログラムは送信が終わるまですべての動作を止めてしまいますが、これは現実的ではなく、実用的には割り込みの併用も必要です。

などなど、本当に使い切ることは容易ではありません。 しかし、こんな感じで動作するんだ、という感覚さえつかめれば、この先を試してみるきっかけにはなるのではないかと思います。



熊谷正朗 [→連絡]
東北学院大学 工学部 機械知能工学科 RDE
[| ]