目次

Cコード記述作法

 mikroCの使い方を説明したときに利用したファームウエアを
 より洗練されたCコード記述に改変してみます。

 元のCコードは、以下。

void main(void)
{
  /* initialize PORT */
  TRISB = 0x00 ;
  /* endless loop */
  while (1) {
    PORTB = 0x01 ;
    Delay_ms(500);
    PORTB = 0x00 ;
    Delay_ms(500);
  }
}

 Cのテキストのバイブルと呼ばれるK&R本には、プログラムの中に
 「magic number」を入れるのを避けるべきと書かれています。



 剥き出しのままの数値では、何をやりたいのかわからないので
 理解しやすい文字列で置き換えて、コード作成者の意図が明確
 になるようにと解釈できます。

 マクロ定義のディレイティブ(疑似命令)を利用し書き換えます。

#define OFF 0
#define ON  OFF+1

#define FIVE 500

void main(void)
{
  /* initialize PORT */
  TRISB = OFF ;
  /* endless loop */
  while (ON) {
    PORTB = ON ;
    Delay_ms(FIVE);
    PORTB = OFF ;
    Delay_ms(FIVE);
  }
}

 Cは関数を定義して、それら利用して全体を作り上げていく
 言語体系をもちます。I/Oの入出力設定を、関数で定義して
 よりわかりやすいコードにしてみます。

#define OFF 0
#define ON  OFF+1

#define FIVE 500

/* prototype */
void usr_init(void);

void main(void)
{
  /* initialize PORT */
  usr_init();
  /* endless loop */
  while (ON) {
    PORTB = ON ;
    Delay_ms(FIVE);
    PORTB = OFF ;
    Delay_ms(FIVE);
  }
}

void usr_init(void)
{
  /* initial value */
  PORTB = OFF ;
  /* direction */
  TRISB = 0x00 ;
}

 記述量は増えていますが、ひとつの関数で扱う内容を
 限定しているため、何をしたいのかが把握しやすく
 なっています。

 機械の動作を扱うファームウエアでは、状態変数を使い
 動作を順序よくこなしていきます。

 状態変数を利用したコードで、書き直すと以下。

#define OFF 0
#define ON  OFF+1

#define FIVE 500

typedef unsigned char UBYTE ;

UBYTE state ;

/* prototype */
void usr_init(void);

void main(void)
{
  /* initialize PORT */
  usr_init();
  /* endless loop */
  while (ON) {
    /* impress */
    PORTB = state & ON ;
    /* delay */
    Delay_ms(FIVE);
    /* update state */
    state++ ;
  }
}

void usr_init(void)
{
  /* initial value */
  PORTB = OFF ;
  /* direction */
  TRISB = 0x00 ;
  /* clear state counter */
  state = 0 ;
}

 状態変数stateは、0から255を繰り返します。

 奇数値でLED点灯、偶数値でLED消灯になるので
 状態により、動作を変えています。

 この場合は、状態変数を使うメリットがないように
 感じると思いますが、次のような処理をすると状態
 変数を使うことの意義がわかると思います。
  1. LED点灯、1秒間待ち
  2. LED消灯、3秒間待ち
  3. LED点灯、0.5秒間待ち
  4. LED消灯、0.5秒間待ち
  5. LED点灯、2秒間待ち
  6. LED消灯、1秒間待ち
  7. はじめにもどる
 ひとつのwhileループ中で処理するのもありですが  0.5秒待ちをひとつの状態にすると、機械的に処理  を記述できます。
  1. LED点灯、0.5秒間待ち(state = 0)
  2. LED点灯、0.5秒間待ち(state = 1)
  3. LED消灯、0.5秒間待ち(state = 2)
  4. LED消灯、0.5秒間待ち(state = 3)
  5. LED消灯、0.5秒間待ち(state = 4)
  6. LED消灯、0.5秒間待ち(state = 5)
  7. LED消灯、0.5秒間待ち(state = 6)
  8. LED消灯、0.5秒間待ち(state = 7)
  9. LED点灯、0.5秒間待ち(state = 8)
  10. LED消灯、0.5秒間待ち(state = 9)
  11. LED点灯、0.5秒間待ち(state =10)
  12. LED点灯、0.5秒間待ち(state =11)
  13. LED点灯、0.5秒間待ち(state =12)
  14. LED点灯、0.5秒間待ち(state =13)
  15. LED消灯、0.5秒間待ち(state =14)
  16. LED消灯、0.5秒間待ち(state =15)
  17. はじめにもどる(state =16)
 点灯処理になる状態変数を抜き出してみましょう。  0、1、8、10、11、12、13  Cには、多方向分岐処理のswitch文が用意されている  ので、点灯消灯は、次のように記述できます。 switch (state) { case 0 : case 1 : case 8 : case 10 : case 11 : case 12 : case 13 : PORTB = ON ; break ; default : PORTB = OFF ; break ; }  状態変数を利用したコードの一部を変更すると  複雑な制御を必要とするファームウエアも意外  に簡単に記述できるとわかります。 #define OFF 0 #define ON OFF+1 #define FIVE 500 typedef unsigned char UBYTE ; UBYTE state ; /* prototype */ void usr_init(void); void main(void) { /* initialize PORT */ usr_init(); /* endless loop */ while (ON) { /* impress */ switch (state) { case 0 : case 1 : case 8 : case 10 : case 11 : case 12 : case 13 : PORTB = ON ; break ; default : PORTB = OFF ; break ; } /* delay */ Delay_ms(FIVE); /* update state */ state++ ; if ( state == 16 ) { state = 0 ; } } } void usr_init(void) { /* initial value */ PORTB = OFF ; /* direction */ TRISB = 0x00 ; /* clear state counter */ state = 0 ; }  ループ処理の中に判定を入れるのは、わかりにくいと  なれば、テーブル参照を使います。テーブル参照には  配列を利用すれば簡単です。 #define OFF 0 #define ON OFF+1 #define FIVE 500 typedef unsigned char UBYTE ; UBYTE state ; UBYTE pat[16] ; /* prototype */ void usr_init(void); void main(void) { /* initialize PORT */ usr_init(); /* endless loop */ while (ON) { /* impress */ PORTB = *(pat+state) ; /* delay */ Delay_ms(FIVE); /* update state */ state++ ; if ( state == 16 ) { state = 0 ; } } } void usr_init(void) { /* initial value */ PORTB = OFF ; /* direction */ TRISB = 0x00 ; /* clear state counter */ state = 0 ; /* store array */ *(pat+ 0) = ON ; *(pat+ 1) = ON ; *(pat+ 2) = OFF ; *(pat+ 3) = OFF ; *(pat+ 4) = OFF ; *(pat+ 5) = OFF ; *(pat+ 6) = OFF ; *(pat+ 7) = OFF ; *(pat+ 8) = ON ; *(pat+ 9) = OFF ; *(pat+10) = ON ; *(pat+11) = ON ; *(pat+12) = ONF ; *(pat+13) = ON ; *(pat+14) = OFF ; *(pat+15) = OFF ; }  Cのプログラムでは、ポインタを利用することが多いです。  ポインタを苦手としている学生は多いですが、コンピュータの  メモリがどうなっているのかとアドレスとデータの関係が理解  できれば使いやすく感じるようになります。  コンピュータのメモリは、アドレスとデータがペアとなっています。  データ型を利用して、変数を宣言すると次のようになります。  typedef unsigned char UBYTE ; UBYTE state ; UBYTE pat[16] ; UBYTE *ptr ;  変数の前に*をつけないと、その変数はデータを表現する  ことになります。  変数の前に*をつけると、その変数はアドレスを表現する  ことになります。  *は、「データではないですよ。」と言う印なので  変数ptrが持っているのは、アドレスになります。  変数は値を変えることができるので、アドレスを変更できる  となり、データを指し示すのでポインタという呼称をつけら  れて、区別されました。  ポインタを使うと、データのサイズ(1バイト、2バイト、4バイト)は  宣言時にデータ型で指定されます。ポインタは、データのサイズに見合った  だけ変化させられるので、アドレスの値を、どれだけ変化させればよいかを  プログラム作成者が考えなくてよくなります。  変数がポインタとなる場合、メモリのアドレスを扱う変数になります。  では、データで宣言された場合、そのデータが格納されているアドレスを  取り出したいときには、どうするのかというとアドレスいうことで「&」  をつけて取り出します。  typedef unsigned char UBYTE ; UBYTE state ; UBYTE *ptr ; ptr = &state ;  ポインタは、3つ以上のデータを「塊」として扱いたいときに便利です。  3つ以上のデータの塊というので、最もわかりやすい例は、配列を  利用したデータの集合体になるでしょう。  データの塊に名前をつけて、その中の0番目、1番目...として  いるので、ポインタでも配列でもデータを入出力できます。  変数ptrがポインタだとすると、ptrの中身はアドレスです。  *をつけると(要するにxをつけると)、アドレスでない  のでデータになります。  (pat+state)というのは、変数patがポインタなので、アドレスに  オフセットstateを加えて、新しいアドレスにします。  *(pat+state)は、*をつけてアドレスでなくなり、データだと  なります。  変数ptrがポインタだとすると、ptrの中身はアドレスになります。  データが欲しいなら、アドレスでない場合に、*をつけるという  約束をつかいます。  自分がCを触り始めた頃は、アセンブリ言語でプログラムを記述する  ことに慣れさせられていたため、ポインタの概念はすぐに理解でき  ました。  現在は、高級言語でプログラムを作成することが多いため、メモリ  のイメージが頭にないため、学生は理解するのに苦しむようです。  メモリのイメージを板書して説明すると理解できるよう。  マイコンを利用したファームウエアの記述スタイルには  定番があります。初期化をしたなら、無限ループの中で  必要な仕事をするカタチです。  手間のかかる処理を関数の中に閉込めて、mainの中は  極力短いステートメントや関数を呼出すように、記述  します。  Cの言語仕様は、proper programを書きやすいように  なっています。proper programとは、次の3構造だけ  で記述されたコードです。  それぞれの構造と構文の対応を簡単に説明します。  連接   やりたいことを上から下に並べる。   やりたいことをステートメントでも、関数でもOK。 /* impress */ PORTB = *(pat+state) ; /* delay */ Delay_ms(FIVE); /* update state */ state++ ; if ( state == 16 ) { state = 0 ; }  選択   「もし○○だったなら△を実行する」を記述します。   「if...else...」の2方向分岐と「switch...case...」の   多方向分岐があります。   ファームウエアの2方向分岐は、次のように記述。 /* default */ tmp = OFF ; /* judge */ if ( state == LAST ) { tmp = ON ; } /* impress */ PORTB = tmp ;   条件不一致の処理を記述し、条件一致の処理を続けます。   この書き方だと、連接と同様に、コードを理解しやすく   なります。  反復   条件が成立している間、処理を繰返します。   「while (...) { }」の前判定反復、「do { } while (...)」   の後判定反復、「for (...){ }」の指定回数反復のいずれか   を使えます。   前判定反復と指定回数反復は、等価な内容記述に交換できます。 for ( i = 0 ; i < LAST ; i++ ) { /* ??? */ } ↑↓ i = 0 ; while ( i < LAST ) { /* ??? */ i++ }  ファームウエアを記述する場合、割込み処理を使うと  見通しがよくなったり、ステートメントの数を減らす  ことができます。その場合の記述は、必要になったら  説明します。

目次

inserted by FC2 system