目次
前
次
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消灯になるので
状態により、動作を変えています。
この場合は、状態変数を使うメリットがないように
感じると思いますが、次のような処理をすると状態
変数を使うことの意義がわかると思います。
- LED点灯、1秒間待ち
- LED消灯、3秒間待ち
- LED点灯、0.5秒間待ち
- LED消灯、0.5秒間待ち
- LED点灯、2秒間待ち
- LED消灯、1秒間待ち
- はじめにもどる
ひとつのwhileループ中で処理するのもありですが
0.5秒待ちをひとつの状態にすると、機械的に処理
を記述できます。
- LED点灯、0.5秒間待ち(state = 0)
- LED点灯、0.5秒間待ち(state = 1)
- LED消灯、0.5秒間待ち(state = 2)
- LED消灯、0.5秒間待ち(state = 3)
- LED消灯、0.5秒間待ち(state = 4)
- LED消灯、0.5秒間待ち(state = 5)
- LED消灯、0.5秒間待ち(state = 6)
- LED消灯、0.5秒間待ち(state = 7)
- LED点灯、0.5秒間待ち(state = 8)
- LED消灯、0.5秒間待ち(state = 9)
- LED点灯、0.5秒間待ち(state =10)
- LED点灯、0.5秒間待ち(state =11)
- LED点灯、0.5秒間待ち(state =12)
- LED点灯、0.5秒間待ち(state =13)
- LED消灯、0.5秒間待ち(state =14)
- LED消灯、0.5秒間待ち(state =15)
- はじめにもどる(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++
}
ファームウエアを記述する場合、割込み処理を使うと
見通しがよくなったり、ステートメントの数を減らす
ことができます。その場合の記述は、必要になったら
説明します。
目次
前
次