20. はじめてのCプログラミング編
C言語だろうがアセンブラだろうがBASIC言語だろうが何でも良かったのですが、AVRのソフト開発関係はどれも無償で環境が揃います。
BASIC言語もMCS社
からBASCOM-AVRというBASICコンパイラがあり無償でフルスペックの内容で使え、4KBまでという制限はあるが広く普及しています。
いきなりLCDパネル取付け文字表示出来るのには驚きで、さらに製品版も15000円以下と内容からすれば格安です。
さて、私はC言語でプログラムが書けるかというと書けません、今後も積極的にプログラミングを行う可能性は低いと言えます。
それでどうするのかというと、やっぱり勉強しか無いんですね任天堂DSも持っていないし「脳トレ」には良いかもしれません。
次からの情報は大変役立たせていただきました、大変参考になると思います。
始める電子回路(始めるAVR)
http://www9.plala.or.jp/fsson/NewHP_elc/Link_nAVR.html
Keinsoft Archives
http://homepage.mac.com/keinsoft/archive/index.html
ここで紹介されているプログラムを始めのベースとさせて頂き、解説や解析をしていくという手法で説明します。
全くの素人が20年以上前に発行の「初めてのC」本を片手とWeb情報から行いますので本末転倒気味の部分はご了承下さい。
良くある「初めてのC」ではなく、手を抜かない少々変わった切り口でかつ(笑)も出てくる「初めてのC」を心がけます。
マイコンというと回路的な内容が多くなりますが、AVRの回路などの説明はその都度説明します。
パソコンで動作させるプログラムは
「初めてのC」などの本では、Hello World をパソコン画面に表示するのが定番となります、そのプログラムは実に短く下記の通りです。
この程度の内容で.EXEフアイルが出来上がり、実行するとパソコン画面にHello Worldと表示されるのも、それはそれで凄いのですがね。
Hello World とPC画面に表示する定番プログラムのソースリスト
void main(void)
{
printf("Hello World!\n");
}
マイコンを動作させるプログラムは
しかしLEDを点滅させるなどの制御系のプログラムの勉強はこの時点で無理があります。だってパソコンみたいに表示する画面も代替えの表示器がありません。
C言語というルールで作るプログラムは決まったルールがあり、厳密にはその手順や決まりに合わせますが、ラフに記述出来るのもCの魅力とも言われています。
既に前項で開発環境を構築した際に作ったAVRマイコンにLEDが1-3個ほど、
プッシュスイッチ1個のハードウエアのコントロールするプログラム程度からの実験勉強となります。
マイコン系の開発用のC言語やプログラム作成で使われるソフト等は、まだまだ日本語が使えない物が多い傾向が強いですので、その方が無難と思います。
実際は11行ですが、プログラムのソースフアイルの記述を見やすくする為に改行などC言語特有で見やすく体裁を整えていますので、
やろうとおもえば行数は減らせます。
待ち時間計測用のiという名前の変数です。
whileとは(ホワイル文 …の間ずっと,)といった意味でwhileループ制御文とも呼ばれます。
ループを回る回数を決められない場合に使われる。
式には通常、ループを回る条件が書かれています。ループの最初に条件判定を行って条件が正しいあいだは(真 True)として何度も回ります。
この行は、先のwhile(1)のステートメント(文)です。
はポートBの入力レジスタの事です。
は代入演算子で、"|="はビット演算子です。複数の演算子を使っているので、複合代入演算子と呼ぶこともあり、演算子の間にスペースは入れれません。
LCDを付けれない事は無いけど取付けるのは少々高等レベルです、つまり上記の様な「基本中の基本のC言語プログラム」は、いきなり却下されてしまいます。
次はAVRでのC言語プログラムの最も基本となる形です、これをベースにAVRのC言語を勉強します。
※赤部分は注釈または解説文
21. C言語で AVR 制御プログラミング
始めが肝心という言葉がありますが、あまりその部分を意識しないで、とにかく例題や資料から情報をもらって作ってみるのが一番と思います。
そのうち色々「この書き方や、やり方は上手な方法でないな?」などと気がつく物です。
そうやって、少々暗中模索も良い経験となると思いますが、いかがでしょうか?
C言語プログラムの最も基本となる形のソースリスト例です。
int main(void)
{
// ここに「初期化処理を記述」
while(1) {
// ここに「繰り返し実行する処理を記述」
}
}
定番はLEDの点灯点滅で(通称=LEDチカチカ)制御プログラム界のHello Worldとも呼ばれています。
次がそのプログラム本体でフアイル名は led_test_first_avr.c としています。
フアイル名は英文字で、かつ小文字を使い、記号はアンダーバーをお勧めします
《動作》次のプログラムはLEDの2個が点灯(PB1=ICの13ピンとPB2=ICの14ピン)し、LEDが1個だけ(PB0 ICの12ピン)点滅するという内容です。
led_test_first_avr.c のソースリスト
int main(void) /* C言語プログラムはmain関数から始まる */
{
volatile long i; /* 待ち時間計測用のiという名前の変数 */
DDRB =0xff; /* 1111 1111=PortBを全部出力モードに設定 */
while(1){ /* 永久ループ*/
PINB |= _BV(PB0); /* PortB0 の値を反転させる =LEDが点滅 */
for (i = 5000; i > 0; i--); /* 点灯/消灯したら、次に行くまでしばらく待つ */
}
return 0; /* main関数を終了させる */
}
この11行を徹底的に噛み砕いてみる
C言語は空白の多いプログラムの異名をとるほどですが、多くのプラグラムの基本となっています、見やすい記述を身につけましょ。
Cプログラムの最低限の書き方の礼儀として代表的なのが次です。
2. プログラムは行を下げて書く。
3. プログラムの意味をまとまりごとに行を空けて書く。
4. コメント(注釈文)を沢山入れる。
1行目は #include <avr/io.h>
"avr/io.h"は
AVRが内蔵しているI/Oポートや各種レジスタのアドレスやビット番号などを定義した、
非常に重要なヘッダ・ファイルのエイリアスで実際のファイル名は"io2313.h"です。(場所は、c:\WinAVR-20100110\avr\include\avr)
この1行を記述すると、プロジェクトで定義したデバイス(ATTyny2313など)に応じたファイルが自動的にインクルード(取り込み)されます。
ファイルの中は400行近いです(内容を見やすく空白行を除いても300行近い)
io2313.hのフィルの中身を見てみる。
この1行目の意味は「毎度決まった400行も書いていられないので、別ファイルにして置き一気に読み込む」
という便利な機能です。
別の情報では"初心者は意識しなくても良い"というような優しい教えもありました。(でも後ですぐ現実は甘くないと判る)
2行目は int main(void)
関数とは「いくつかの命令文の集まり」で、何らかの処理を行うために必要な命令を書き並べたもので、
C言語は関数を組み合わせてプログラムを記述するといっても過言ではありません。
C言語はmain関数から実行されます、だから必ずmain()と書きます、しかしプログラム中で意識してmain関数を使う事はまず無いです。
C言語仕様に「型を省略した関数はint型として解釈される」という規約がありmain(void)など省略形もあります。
引数は関数を使うときに、関数に渡す情報で"void"は関数の戻り値や関数の引数がないことを明記する「型」の一種です。
戻り値とは、関数内の処理が終わったとき、関数側から返される情報です。これは関数内でreturn文によって返されます。
main関数の最後に書いている「return 0;」(11行目)が、戻り値を返す命令で「戻り値として0を返す」という意味だったのです。
「初期化処理」は、プログラムが作動し始めるときに、一度だけ実行される処理です。
「繰り返し実行する処理」は初期化が終わって、電源が切られるまで繰り返し実行される処理を記述します。
マイコンのプログラムの場合、通常は電源を切るまでは何らかの処理を繰り返し実行させるため、"main()"関数を
終了することはありません。
3行目は {
2行目の int main(void) とセットですね。
}
プログラムを書くときこの始まりの { と
終わりの}の関連が判る様にソースコードを書く際にTABキーを入れたりして見やすく体裁を整えます。
この3行目の { に対しての } は12行目となります。
4行目は volatile long i ;
別にiをaとかavr_cpuとかでも良い。
変数名は何でも良いのですが、アルファベットと数字と記号ですが、記号は_(アンダーバー)がよく使われますね。
また始めの1文字は必ずアルファベットでなくてはいけません。
Volatileとは、ハードウエアの信号等を確実に繰り返しリードする必要がある場合などにVolatile変数宣言した変数を使用します。
コンパイラのプライオリティ(優先度)が高い場合は、最初に1回だけ信号を内部CPUレジスタにリードして、
2回目以降はレジスタに読み込んだ1回目のデータをリードデータとして使用してしまう事があるので、
それを防ぐ(確実にハードウエア(メモリ)などをアクセスするようにする)ために、Volatile宣言を使用します。
またvolatile変数とは、プログラム以外で変数の内容が書き換えられる変数を意味します。
例として入力ピンや内蔵タイマのレジスタを変数とした時で、このような変数はvolatie宣言をして、
コンパイラの最適化の対象にはならないようにする必要があります。
longとは、long型(4バイト分の大きさの入れ物)です
(signed)long(符号付き) -2,147,483,648〜2,147,483,647(±約21億)
unsigned long(符号無し) 0〜4,294,967,295(約42億)
整数型でプログラムのソースコードにおけるのデータ型の1つまたは1群で、整数を取り扱うものです。
文の終わりは、セミコロン ; で表します。
5行目は DDRB =0xff; /* PortBを全部出力モードに設定 */
DDRは「ポート方向レジスタ」データの出入りする方向を決定するためのレジスタで、
Bポートを入力信号用として使うかまた出力信号用として使うかを決定します。
出力にしたい場合は1で、入力にしたい場合は0とします。
bit 7 6 5 4 3 2 1 0
DDRB
DDB7
DDB6
DDB5
DDB4
DDB3
DDB2
DDB1
DDB0
/* と */ の間にはコメント(メモ)が書けます、日本語も使えます。
6行目は while(1){ /* 永久ループ*/
書式は次の通りで
while(継続条件式)
ステートメント(文) /* 繰り返す処理 */
条件が正しくなければ(偽)としてループを抜け次へ進みます。
ステートメントが複数の場合は { } ではさみます。
C言語の(真)は0以外の値です。そのため while(1) は常に(真)となり、
必ずループされる「無限ループ」になります。
この説明から( )内の数字は0以外の値ならなんでも良いことになりますが、普通は1と書きます。
この7行目の " { " に対しての " } " は10行目となります。
7行目は PINB |=_BV(PB0); /* PortB0 の値を反転させる = LEDが点滅 */
ステートメントとは「プログラムを作るのに必要なら命令語」となっていますがピンときませんね。
" ; " までのひとまとまりがステートメントです。
ステートメントは次に分類され、種類も10種類程度で他の言語から比べ少ないのが特徴です。
分類
1.「式」 a=0; や i==; などの演算子を使って表されるもの。
2.「複文」 式や他のステートメントが複数集まった物。 { } ではさまれる。
3.「空文」 式があるはずの所に何も無いとき。 ; だけ書かれる。
種類(ここでは名前だけ紹介しておきます)
1. if(式)ステートメント
2. if(式)
ステートメント
else
ステートメント
3. while(式)
ステートメント
4. do
ステートメント
while(式)
5. for(式1; 式2; 式3)
ステートメント
6. switch(式){
ステートメント
case 定数式 :
ステートメント
default :
ステートメント
}
7. break ;
8. continue ;
9. return 式 ;
10. goto 識別子 ;
・・・・・
識別子: 文
PINB
|=
"|"はビットごとの ORを意味します、いずれかのビットが 1 なら結果が 1 になるビット演算です。
"="とは代入演算子ということになり、代入演算子の基本となるものは "=" です。
意味は a = a | b ( aとbの論理和をaに代入する )と専門書には書かれており、"="は右辺の値を左辺に代入します。
算数が苦手な自分としては「演算子」と聞いただけで引いてしまいます「演算死」という感じですが、すすめて行きます。
論理和はORと呼ばれ、入力値のいずれかに1が入力された場合に1を出力し、それ以外の場合には0を出力します。
つまりここでの"|="とは
PINB |=_BV(PB0); の右側の値は「_BV(PB0)」で左側の値は「PINB」ということになります。(本当かなぁ?)
真理値表
入力A
入力B
出力
0
0
0
0
1
1
1
0
1
1
1
1
_BV(PB0)
で先の右側の値である _BV(PB0) ですが、これは悩みました、いきなり" _ " アンダーバーから始まるんですからね(笑)
この部分は1行目で説明というか紹介した avr/io.h と同じく、別ファイルの iotn2313.h 内容等と関連するようです。
" _BV " があります。この iotn2313.h ヘッダフィルの中のコメントにヒントがありました。
《直訳すると、解る様なさらに深くなる様な...》
/* The Register Bit names are represented by their bit number (0-7).(
登録のビット名は、そのビット番号(0-7)で表される。)
*/
/* Example: PORTB |= _BV(PORTB7); Set MSB of PORTB.(
例:ポートB | = _BV(PORTB7); PORTBのMSB設定が。)*/
また、
C:\WinAVR\avr\include\avr\sfr_defs.h を見ると#define _BV(bit) (1 << (bit)) などとあったりします。(発見まで1週間)
調べると"_BV "はC言語のビット操作のマクロの事だそうです。(AVRwikiより参照)
#defineを使うと、ソース上の文字の並びを、他の文字の並びに置き換えることができました
マクロはソース上で何度も現れる意味のある定数は、そのまま使わずに記号定数としておいた方が分かりやすく、後から修正するときに楽になります。
#defineが行うこのような置換をマクロ置換(オブジェクト形式マクロ)といいます。
たとえば、次等はもの凄くどうなるか解りますよね、MAXは46という意味です。後でMAXの値が変わっても混乱しません。
#define MAX 46
AVR各レジスタのビット名とビット番号(0〜7)の対応が定義されています。
入力ポートPINBについてPINB0〜PINB7の名前で定義されています。
ちなみに
PORTBのbit0〜bit7に対応しPB0〜PB7という名前が定義されています。
つまり _BV() という物に対して BP0 というポートBのbit0を指定しているという事になります。
AVRは変則的なポート設定方法になっているともいわれており(他のマイコンが正しいとすればの話ですが)次の様なパタンがあります。
方向設定レジスタ(DRR×)を出力に設定にして、入力用データレジスタ(PIN×)で1にすると出力データが反転になる。
ということで、このプログラムはこの仕組みを使った物で、具体的に本プログラムに置き換えると。
先の5行目の説明である方向設定レジスタ(DRR×)の設定は次の通りで出力に設定されています。
DDRB = 0xff; /* PortBを全部出力モードに設定 */
この7行目が
PINB |=_BV(PB0);
となっており、PINBの値が1になるとポートBのbit0のデータが反転する
さてもう一度7行目全体ですが
PINB |=_BV(PB0);
は、ポートBの入力レジスタ(PINB)のPortB0の値だけを反転させるという意味になります。
ちなみに、次のパタン等もある様です。
PINB |= _BV(PB0); // PB0ピンだけをHighにし、残りは変化させない
PINB = _BV(PB0); // PB0ピンだけをHighにし、残りをLowにする
PINB = ~_BV(PB3); // PB3ピンだけをLowにし、残りは変化させない
MSBとは何か?
せっかくなので先の iotn2313.h ヘッダフィルの中のコメントからのヒントでしたがMSBとは次のようです。
MSB(Most Significant Bit/Byte)とは:最上位ビット
2進数データの最上位のビット。またバイト列のうち最上位のバイト。最下位のビット/バイトはLSB(Least Significant Bit/Byte)という。
「01110101」というビット列のMSBは左端の0であり「b1765a8f」というバイト列のMSBは左端のb1。
LSB(Least Significant Bit)とは:最下位ビット
2進数データの最下位のビット。またバイト列のうち最下位のバイト。最上位のビット/バイトはMSB(Most Significant Bit/Byte)という。
「01110101」というビット列のLSBは右端の1であり「b1765a8f」というバイト列のLSBは右端の8f。
ビットではなくバイトと読んだ場合は
MSB(Most Significant Byte) : 最上位バイト
LSB(Least Significant Byte) : 最下位バイト
ということになります。1を4バイトで表現した場合、バイトが以下のように並ぶと考えると、次となります。
0x00 0x00 0x00 0x01
(MSB) (LSB)
8行目は for (i = 5000; i > 0;i--); /* 点灯・消灯時間の調整 */
forはforループ文と呼びます。ループを回る回数が解っている場合に使います。
対して6行目のWhileループ文があり、ループを回る回数が解らない場合に使います。
forループ文の書式は次の通りです。
for(初期値(初期条件) ; 判断条件(繰り返し条件) : 増分)
ステートメント
i=5000;
これは変数iに5000という初期値を割り付けるという意味です。
変数iはループの繰り返し回数をカウントしていることが分かりますか?言い換えると、いつループを終了させるのか、
あるいはループを続けるのかを制御する役割を持っています。そのため変数iを、ループ制御変数 と呼ぶことがあります。
ループ制御変数の名前は、昔から i が使われてきました。プログラム中でループ制御変数が2つ以上必要なら次は j 、
次は k という名前を使いそうすることが一般的です。
ループを制御すること以外に重要な役割を兼任しているなら、それが分かるような名前を付けるべきです。
i > 0;
これは判断条件(繰り返し条件)です。
iは変数i ※変数iは先に5000という値となっている。
>は比較演算子で「より大きい」という意味で、他に <「より小さい」 <=「以下」 >=「以上」の4種類があります。
0は(ゼロ)
i--「i--」とは簡単にいえば変数iを-1するという意味でデクリメント演算子と呼びます。
また同様に
「i++」とは簡単にいえば変数iを+1するという意味でインクリメント演算子と呼びます。
C言語ではこの演算子があるので「i = i - 1;」などとは書かずにデクリメント演算子を使うことが一般的です。(詳しくは後参照)
ここで8行目をまとめると
for (i = 5000; i > 0;i--);
変数iが5000から初め、iを-1しながらiが0より大きい間くり返すという処理で、
無駄とも思える繰り返し処理を行いLED点滅間隔を得るタイマー処理を行っています。
インクリメント演算子とは
「++i」は意味としては「変数iの値を+1する」というものです。「++」をインクリメント演算子 といいます。
また、+1する動作をインクリメントといいます。
インクリメント演算子は、次の2通りの形式が存在します。
++変数名 または 変数名++
どちらも「変数の値を+1」で単独の式で使う場合意味は変わりません。
ところが代入式と組み合わせると意味が変わってきます。
ans = ++num;
ans = num++;
仮に変数numに10が格納されていたとします。前者の意味は「変数numの値を+1して、その結果を変数ansに代入する」です。
後者の意味は「変数numの値を変数ansに代入してから、変数numの値を+1する」です。
前者は「ans = 11, num = 11」、後者は「ans = 10, num = 11」になり、この違いを利用したプログラムを書けるようになります。
前者のように演算子の方が変数名の前に来る場合を、前置インクリメント、後ろに演算子が来る場合を後置インクリメントといいます。
for文の「再設定式」はインクリメント動作を行うことが多いです。代入式でさえなければ「++i」でも「i++」でも構いません。
どちらの形式もよく使われています(ただし、結果を代入する場合には、前置インクリメントの方が効率的です。
その意味で、本サイトでは前置インクリメントの方を優先して使います)。
デクリメント演算子とは
+1するインクリメント演算子があれば、-1するものもあります。これがデクリメント演算子です。
デクリメント演算子は「--i」あるいは「i--」と書きます。-1する動作をデクリメントといいます。
インクリメント同様、演算子が前に来る場合を前置デクリメント、後ろに来る場合を後置デクリメントといいます。
+1するのか-1するのかの違いだけで、インクリメントと全く同じです。よって、代入文の場合は「--i」と「i--」で意味が異なります。
とりあえず、これからは+1や-1するようなときには、インクリメント演算子やデクリメント演算子を使うようにしましょう。
for文の省略記法
while文と全く同じになります。さらに条件式まで省略すると、次のような形になります。
これは無限ループです。for文で無限ループを作る場合は、このように書くのが普通です。
for(;;)
{
}
9行目は }
この9行目の " } " に対しての " { " は6行目となります。
10行目は return 0;
main関数を終了させるという意味で、成功時に0(ゼロ)を返すのは、C言語の「お約束」です。
main 関数は、return を省略すると 0 を返す仕様です。
main関数を終了させると、再びmain関数を開始するという事でしょうか?(調べています)
11行目は }
main関数の終わり
この12行目の " } " に対しての " { " は3行目となります。
とここまでで、一応11行目までの説明 + 少々が終わりです。
まだ全文加筆修正中です(けっこう後読みしたら、意味不明な所があるものですね、すみません)
●「初めての次のCプログラミング編」へ続きます。2010.12 作成準備中